Blogs

Valery Silaev

Write once, cross fingers and run... anywhere...
Valery Silaev 
Business Card
Company: SaM Solutions
Posted on May. 16, 2006 05:02 AM in Java Programming, Standards, Emerging Technologies

Subscribe.Subscribe
Print. Print
Permalink Permalink
Share

Instead of intro

This topic assumes that reader has above average Java skills, and, at least, creating own class loader does not sound to him as terrible hack or Masonic mystery.

Where all problems start...

As it sometimes happens, after some upgrade of third-party library my code stops working. Who knows when I will discover this issue with class loading otherwise, but if it soooo itches -- it will be scratched, immediately ;)

You know, any of us never writes bugs ;) So the reason should be definitely with third-party library. Ok, I get ClassNotFoundException (CNFE below) and was almost sure that they did something dumb, because my application works perfectly with previous version. I was sure that they used wrong class loader - anyway, what else could happen???

After several hours of debugging I noticed subtle difference in library code. Instead of ClassLoader.loadClass(clsName) they start to use Class.forName(clsName, init, classLoader). In both cases class loader was correct - it was my own (bugless, remember ;) custom class loader. However, forName throws CNFE. I trace execution sequence - my class loader is invoked, it loads class successfully, but CNFE was still thrown! What the heck?

Root of all classes, root of all evil...

After googling for "forName AND loadClass" in Sun BugDatabase I still get nothing. Except 2 observations:

  1. Try the search yourself, how many references are you getting? It is amazing that functionality that lies at very core of Java causes such amount of bug reports.
  2. Any stack traces I see has roots inside of ClassLoader methods (somewhere below ClassLoader.loadClass), in my case root of exception was native method Class.forName0.

So I write a standalone test program, that emulates my class loading behavior and interactions between my code and library code. Same error! Replacing Class.forName with ClassLoader.loadClass yields correct result!

So the problem is class loader itself. If you study accompanied sample code, you may notice that very specific class loading scheme is used. Unlike classic Parent-Child relationship my class loader uses something like Self-Siblings-Parent scheme. Former one is standard: first class loader queries parent class loader to load class, then if not found it tries to load class itself. My scheme is more complex: first try to map supplied name of class ("synonym") to actual class name, and, if found, uses parent class loader to load bytecode, but defines class itself (class loader, that is used for defineClass will be returned by clsInstance.getClassLoader). If synonym is unknown at this step, then my class loader asks siblings to load class by synonym. If they also fail, then class loader decides that name is not synonym but rather regular class name and delegates call to parent. Tricky? Yes. Forbidden / Incorrect ? No. Otherwise please point me to JLS / API docs where this is stated explicitly.

So, if you are invoking Class.forName(name, init, classLoaderA) it is possible that class is loaded by some classLoaderB, that is neither classLoaderA nor any parent on chain of classLoaderA.

Don't ask me why I've created such class loading scheme. However, similar could be found with JBoss "Unified Class Loader". Also I'm not sure, but SAP WebAS has to resolve references (remember? library, interface, service references...) in similar way. So this is not exceptional case.

But why?..

As I mentioned above, all that jazz happens in native Class.forName0 method. I have no sources for this method, but from results I got I may conclude that for Sun JVM (vendor is important!, see below) it looks like the following in Java:
 


private Class forName0(String clsName, boolean init, ClassLoader originalCL)
  throws ClassNotFoundException
{
  /* loadClassInternal delegates to loadClass */
  final Class cls = originalCL.loadClassInternal(clsName, init);
  final ClassLoader actualCL = cls.getClassLoader();
  for (ClassLoader x = originalCL; x != null; x = x.getParent())
  {
    /* Identity equality or equals, who knows ;) */
    if ( x == actualCL) return cls;
  }
  /* 
   * Class is loaded by class loader that is 
     outside class loaders hierarchy of originalCL
  */
  throw new ClassNotFoundException(clsName);
}

At least, this is observable behavior (handling null as bootstrap class loader is omitted). Now the question are: 1) where this behavior of Class.forName is exactly described; 2) where there is at least a notice that such check could ever happen; 3). does Sun understands, that this way they implicitly forbid any class loading scheme besides classic Parent-Child?

I've submitted this issue as bug to Sun BugDatabase, but it takes 3 weeks for review. Let us see what will follow.

Just pray that it works expected way...

Menwhile, I've already get answers to questions 1 and 2 myself: the post-loading checks in Class.forName are totally unspecified, and may yield different results when using JVMs by different vendors.

Below are results of my JVMs shootout. You may add your own results, just download sample program and submit output of 3 command lines:
 
 
java -version
java -jar cl_test.jar
java -jar cl_test.jar error


 

Second command uses version with ClassLoader.loadClass, and it works identically with all JVMs. Third line force usage of Class.forName and results are varying.

Sun JVM 1.4.2 Windows
Java 5 produces the same results, so I've omitted it

java -version
java version "1.4.2_06"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_06-b03)
Java HotSpot(TM) Client VM (build 1.4.2_06-b03, mixed mode) 

java -jar cl_test.jar
MODEL CL: #27355241
This: #12115735
Class: j5cl.main.impl.Part_01 #20876681
ClassLoader: #31866429
This: #27837671
Class: j5cl.main.impl.Part_02 #18296328
ClassLoader: #31866429
This: #30223967
Class: j5cl.main.impl.Part_01 #27235645
ClassLoader: #24287316
This: #32429958
Class: j5cl.main.impl.Part_02 #25669322
ClassLoader: #24287316
This: #14978587
Class: j5cl.main.impl.Part_02 #25669322
ClassLoader: #24287316

java -jar cl_test.jar error
MODEL CL: #27355241
java.lang.IllegalArgumentException: Unnable to load implementation class: app.PartA
	at j5cl.main.impl.ThingClass_ForName.createPart(ThingClass_ForName.java:30)
	at j5cl.main.Test.main(Test.java:42)
Caused by: java.lang.ClassNotFoundException: app/PartA
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:219)
	at j5cl.main.impl.ThingClass_ForName.createPart(ThingClass_ForName.java:13)
	... 1 more
Exception in thread "main"
IBM JVM 1.4.2 Windows
I like this one: the check is either even more strict or just na?ve. Anyway, error message deserves applauds - it is totally self-explanatory

java -version
java version "1.4.2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2) 
Classic VM (build 1.4.2, J2RE 1.4.2 IBM Windows 32 build cn1420-20040626 (JIT enabled: jitc))

java -jar cl_test.jar
MODEL CL: #1934690227
This: #2072987570
Class: j5cl.main.impl.Part_01 #1776109491
ClassLoader: #1934985139
This: #2104821682
Class: j5cl.main.impl.Part_02 #1777911731
ClassLoader: #1934985139
This: #18188210
Class: j5cl.main.impl.Part_01 #1777551283
ClassLoader: #1935017907
This: #50939826
Class: j5cl.main.impl.Part_02 #1777190835
ClassLoader: #1935017907
This: #52758450
Class: j5cl.main.impl.Part_02 #1777190835
ClassLoader: #1935017907

java -jar cl_test.jar error
MODEL CL: #1934854089 
Exception in thread "main" java.lang.NoClassDefFoundError: Bad class name 
(expect: app/PartA, get: j5cl/main/impl/Part_01)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:260)
        at j5cl.main.impl.ThingClass_ForName.createPart(ThingClass_ForName.java:13)
        at j5cl.main.Test.main(Test.java:42)
Sun(?) JVM 1.3.1 Mac
Top of stupidity - duplicate classes may happens with single class loader, not with separate ones

java -version
java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition 
(build 1.3.1-root_1.3.1_020714-12:46)
Java HotSpot(TM) Client VM (build 1.3.1_03-69, mixed mode)
[iMac-400-Users-Computer:/shara]

java -jar cl_test.jar
MODEL CL: #6489619
This: #1795395
Class: j5cl.main.impl.Part_01 #1387762
ClassLoader: #3496821
This: #6160135
Class: j5cl.main.impl.Part_02 #1205273
ClassLoader: #3496821
This: #3817081
Class: j5cl.main.impl.Part_01 #6000338
ClassLoader: #6196891
This: #3238994
Class: j5cl.main.impl.Part_02 #4106754
ClassLoader: #6196891
This: #727841
Class: j5cl.main.impl.Part_02 #4106754
ClassLoader: #6196891

java -jar cl_test.jar error 
MODEL CL: #6489619
This: #6966304
Class: j5cl.main.impl.Part_01 #2873465
ClassLoader: #4138247
This: #2039515
Class: j5cl.main.impl.Part_02 #2291301
ClassLoader: #4138247
Exception in thread "main" java.lang.LinkageError: duplicate class
definition: j5cl/main/impl/Part_01
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:190)
        at
j5cl.main.impl.ThingClass_ForName.createPart(ThingClass_ForName.java:13)
        at j5cl.main.Test.main(Test.java:44)
BEA JRockit 7.0 / 1.4.0
Yes! The winner! As an option, your code may even run correctly!

java -version
java version "1.4.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0)
BEA Weblogic JRockit(R) Virtual Machine 
(build 7.0-1.4.0-win32-GARAK-20020830-1714, Native Threads, Generational Concurrent Garbage Collector)

java -jar cl_test.jar
MODEL CL: #4719
This: #15
Class: j5cl.main.impl.Part_01 #17
ClassLoader: #4721
This: #19
Class: j5cl.main.impl.Part_02 #21
ClassLoader: #4721
This: #23
Class: j5cl.main.impl.Part_01 #25
ClassLoader: #4723
This: #27
Class: j5cl.main.impl.Part_02 #29
ClassLoader: #4723
This: #31
Class: j5cl.main.impl.Part_02 #29
ClassLoader: #4723

java -jar cl_test.jar error
MODEL CL: #4719
This: #15
Class: j5cl.main.impl.Part_01 #17
ClassLoader: #4721
This: #19
Class: j5cl.main.impl.Part_02 #21
ClassLoader: #4721
This: #23
Class: j5cl.main.impl.Part_01 #17
ClassLoader: #4721
This: #25
Class: j5cl.main.impl.Part_02 #21
ClassLoader: #4721
This: #27
Class: j5cl.main.impl.Part_02 #21
ClassLoader: #4721

 
Nice diversity of options: checked exception (ClassNotFoundException), runtime error (NoClassDefFoundError), dumb runtime error (special subclass of runtime errors ;) and correct execution! Do you think this should be left "as is"?

You may download sample program here (use "Save as...):
Executable JAR
Sources (rename to *.zip)

Valery Silaev   currently working as lead software engineer for SaM Solutions.


Comment on this articlePlease share your results! I'm very interesting in results of Apple's own JVM and IBM JDK 1.5
Comment on this weblog
Showing messages 1 through 4 of 4.

Titles Only Main Topics Oldest First

  • BEA JRockit get wrong result with forName
    2006-05-17 00:59:43 Valery Silaev Business Card [Reply]

    Actually, I didn't notice error with JRockit implementation of Class.forName.


    Also it runs without errors, take a look at output of both runs with/without "error" parameters:
    -- without "error" there are 3 unique IDs of class loaders, one for model (#4719), for first set of synonyms with own class loader (#4721), for second set with distinct class loader (#4723)
    -- "error" run shows model class loader #4719, and one class loader (#4721) for both sets.


    So neither of JVM tested works right with Class.forName, but all gives stable predictable results with ClassLoader.loadClass()


    P.S. You may alter j5cl.main.Test class to make this error explicit, just add the following at the end of "main" method:
    System.out.println
    (
    model.createPart("app.PartA").getClass() ==
    model.createPart("app.PartOne").getClass()
    ?
    "Error: same class in different class loaders"
    :
    "Ok, different classes"
    );


    VS


    • BEA JRockit get wrong result with forName
      2006-05-23 20:15:22 Som kolli Business Card [Reply]

      Hi Valery,


      Just a quick thing:


      We were using Class.forName("com.sap.portals.jdbc.oracle.OracleDriver") for Oracle drivers on SP12, it was working fine. but now as soon as we upgraded to SP16, it stopped working. Do you know if anything has changed


      Class.forName("com.sap.portals.jdbc.oracle.OracleDriver")


      Connection conn = DriverManager.getConnection(jdbcUrl, system, getJdbcpw());


      Appreciate your time


      Som

      • RE: Oracle JDBC driver and forName
        2006-05-24 00:31:50 Valery Silaev Business Card [Reply]

        Som,


        I don't know exact details with Oracle driver, but it is common that driver class register itself in DriverManager via some code in _static_ block. This block executed when class is initialized, not when class is loaded.


        So try the following instead:
        Class.forName("com.sap.portals.jdbc.oracle.OracleDriver", true /*init*/, this.getClass().getClassLoader());


        Valery


Showing messages 1 through 4 of 4.