 |
Bitten by the class literal change in Tiger
Posted by mister__m on August 16, 2006 at 10:02 PM | Comments (8)
If you are a returning reader, you're probably aware of the enum implementation I wrote for Java 1.4 almost three years ago. Running some Java 1.4 compatible code compiled with Java 5 has just called my attention to a supposedly low impact change that was implemented in Tiger.
Since Java 5, a class literal, i.e., an expression such as MyClass.class does not trigger class initialization anymore. This is a well known, documented issue, but solutions are not clean nor cover all previously supported scenarios.
Let me explain how my enum implementation is affected by this bug so you can understand if this bug might affect your Java 1.4 code. Enum's sole constructor registers the newly created instance's in a few internal Map cache structures. I mean, when something like this is executed:
public final class MyEnum extends Enum {
public static final MyEnum A = new MyEnum("A");
public static final MyEnum B = new MyEnum("B");
private MyEnum(final String name) {
super(name);
}
}
, cache is populated to tell what is the domain for MyEnum (A and B) and to be able to tell you that the instance that represents the String constant "A" in runtime for MyEnum is MyEnum.A
Ok, now that we have a real world example, let's see what worked with Java 1.4 and fails with Tiger and Mustang:
MyEnum m = (MyEnum)Enum.get(MyEnum.class, typedValue);
With Java 1.4, it works; with Java 5, you will get null. The reason is simple: A and B are never instantiated because the class is never initialized.
Now, to the shocking news: there is no way to tell whether a Class instance has been initialized nor a 100% reliable way to force it to initialize. Workarounds such as:
Class c = MyEnum.class;
Class.forName(c.getName(), true, c.getClassLoader());
will require you to handle an exception that should never been thrown in this situation (ClassNotFoundException) and will fail if c.getClassLoader() == null and your code haven't been granted the RuntimePermission("getClassLoader") permission.
Therefore, I've submitted a RFE to add Class.initialize() and Class.isInitialized() to Java SE. I really would like to see this implemented for Java SE 6, but I think it will have to wait for Java 7. This change involves messing with shared native C code and refactoring a lot of stuff on the way, so you are welcome to help me in this task in the JDK Collaboration project.
For now, beware of code that relies on class literal triggering class initialization and hope the ugly workaround above works for you if you cannot avoid it.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Michael,
How about a discussion of alternatives? Here are some:
Class.forceInit(klass)
an annotation marking classes that depend on this sort of initialization
a more agressive class loader
the JSR 277 people might have a better idea
Since you've thought about this, those are all probably inferior solutions. Still, it would be good to explain why.
- Curt
Posted by: coxcu on August 18, 2006 at 07:01 AM
-
For any class that you want a .initialize() and .isInitialized() methods, you already have 'em! Just add the following static methods to the class of interest:
public static void initialize() { }
public static boolean isInitialized() { return true; }
Now I understand this doesn't let you initialize arbitrary classes, but I don't think that's what you're looking for. Aside from your enum class, where else is forcing initialization useful? I've never needed it.
Posted by: jessewilson on August 18, 2006 at 08:25 AM
-
Hi Curt,
Here we go:
If you are trying to initialize a Class instance, this should clearly be an instance method.
I want to initialize the instance whenever I want, not at the beginning or when the variable is first referenced inside my method.
Without the source code, it is hard to tell, but I doubt this does something different from Class.forName(c.getName(), true, c.getClassLoader()) for each class.
Maybe they do, but since I haven't heard from them, I've left my own idea :-)
Posted by: mister__m on August 18, 2006 at 09:18 AM
-
Hi Jesse,
We need a general-purpose way of initialization classes and checking for their initialization status. It is the only thing possible by "regular" ways that is missing in reflection (that I can think of, obviously).
Posted by: mister__m on August 18, 2006 at 09:20 AM
-
In a certain sense I don't see how having an annotation runs counter to having an initialise method. Maybe as a class designer I don't want to wait for the user to decide when my statics are initialised:
@InitialiseOnLoad
static{
blabber blabber blabber
}
for others, initialise() could be good enough.
leouser
Posted by: leouser on August 18, 2006 at 09:26 AM
-
Hi Michel. This is an interesting problem, although I'd say that the change is for the better: the JVM became lazier in classloading, which only benefits start-up performance - it's a feature not a bug ;-)
And 100% of authors of "best practice" guidelines will scream that designs that have any dependence on classloading are evil and ugly... in the Java EE platform you are even forbidden to use non-final static fields. Your code uses final fields, but you're walking on a thin line because the initialization of your fields is not trivial, it triggers side effects on other fields in a different class, this is certainly a scenario that the Java EE specs / blueprints would forbid explicitely if they had though harder on this subject. Classloading and static initialization have other subtle pitfalls, for example you can have circularity bugs, you can have really bizarre JMM-related bugs (scan the jsr166 mailing lists for this)... my take on this is that the JVM spec should be even more relaxed in the promises it makes wrt initialization order, otherwise it creates problems for optimization. A smart static compiler, or even a JVM with classloading optimization like HotSpot's CDS, should be able to "execute" the initialization of most static fields (*) at compilation or pre-loading time, and embed in the resulting binary file the finished layout of constructed objects - this would be very good for startup time. We are always complaining of how long Java apps take to boot, but we don't want to make some small compromises to achieve near-native loading time.
(*) In addition to trivial constructors (that after inlining, doesn't invoke any APIs, to not risk doing I/O or other environment-dependant stuf), the optimizer could handle certain important cases, like APIs that read resource bundles or Images. One big reason for the slow startup of some fat java apps is static initializers that read lots of resources, creates tons of listeners... (SwingSet2, anyone?). And I mean not only the time to show the app's initial functions, which is not bad if those initializations are scattered by many classes loaded on demand, but also the time to show each component for the first time.
In fact, if I was to fix anything in the JVM spec, I would enhance the classfile's constant pool so it is able to store constructed objects and arrays, at least within the limits of initializations that can be resolved at compilation time. Add this to an annotation or other mechanism to mark any object as read-only, and you start approaching the benefits of native code formats like ELF and COFF, with TEXT sections that store most static / read-only data in a way that's quick to load and easy to share between multiple processes (another issue of Java).
Posted by: opinali on August 18, 2006 at 12:27 PM
-
Michael,
No need to change the class from the example. Just do:
MyEnum m = (MyEnum)Enum.get(MyEnum.A.getClass(), typedValue);
Regards,
Claus Nordahl
Posted by: cnordah on August 18, 2006 at 01:12 PM
-
Yes, Claus, but this is a hack, requires changes to all occurences in client code and still doesn't solve the fundamental flaw in java.lang.Class that causes this bug.
Posted by: mister__m on August 18, 2006 at 01:47 PM
|