Java Forum / General / February 2006
Java 5 classloader not making static calls.
Kenny - 13 Feb 2006 18:12 GMT Hello,
I'm having the problem that classes in java 1.5 do not seem to run their static elements until the class is first used.
For example in java 1.4 simply making the Class object would cause the static's to run: class = a.b.c.TestClass.class
In java 1.5 this no longer happens until you do something like: variable = a.b.c.TestClass.staticField;
The TestClass contains things like: static public Object staticField = blah();
I've been searching for information on why this is, or how to have the class initalize itself without having to know what type of class it is. Can anyone offer any suggestions?
Thanks in advance,
Kenny
Chris Uppal - 13 Feb 2006 20:00 GMT > I'm having the problem that classes in java 1.5 do not seem to run > their static elements until the class is first used. > For example in java 1.4 simply making the Class object would cause the > static's to run: > class = a.b.c.TestClass.class Hmmm, that's nasty.
I believe that it's because class literals are now compiled into a single ldw bytecode (with newly extended semantics) rather than into (indirectly) a call to Class.forName(). If you compile code that uses a class literal with a 1.4 JDK, or use the -target 1.4 flag with the 1.5 JDK, then the old-style bytecodes are emitted, and initialisation takes place as before.
The JLS3 is completely unambiguous that this new behaviour is correct (and therefore that JDK 1.4 was buggy in this respect), but I suspect that's because the language of Section 12.4.1 has simply not been updated for class literals (and therefore was incorrect when applied to JDK 1.4 too). That's to say, think it's a bug (unless someone can show that nothing "bad" can possibly happen because a class has not been initialised as a result of referring to its class literal).
As to how to fix it, calling simple methods of the Class object don't have any effect, so the only workaround I can think of is to execute something like:
Class c = my.stuff.Example; Class.forName(c.getName(), true, c.getClassLoader());
which is not quite ideal...
Can you describe what problems this new behaviour causes for you ?
-- chris
Kenny - 13 Feb 2006 20:24 GMT Thank you very much for your reply. That worked perfectly (although I agree, not idea at all).
I can better explain the problem by sending a little code, but I must prefix it with the fact that I did not write this.
The code is basically an Enum, and a global enum repository. It looks basically like (I'll cut out what I can):
class AEnum { private static AEnum One = (AEnum) EnumRegistry.add(new AEnum("One")); private static AEnum Two = (AEnum) EnumRegistry.add(new AEnum("Two")); // private constructor }
class EnumRegistry { private static Map classMap = new HashMap(); public static synchronized Object add(Object enumerationObj) { java.util.HashMap enumMap; // A mapping for a particular class, value->enum
if (classMap.containsKey(enumerationObj.getClass())) { enumMap = (HashMap) classMap.get(enumerationObj.getClass()); } else { enumMap = new HashMap(); } enumMap.put(enumerationObj.toString(), enumerationObj); classMap.put(enumerationObj.getClass(), enumMap); return enumerationObj; }
public static synchronized Object getEnum(Class enumClass, String value) throws EnumException { HashMap enumMap = (HashMap) classMap.get(enumClass); if (enumMap == null || !enumMap.containsKey(value)) { throw new EnumException("enum value: '" + value + "' not found"); } return enumMap.get(value); } }
The code that ends up calling this basically goes: AEnum blah = (AEnum) EnumRegistry.getEnum(AEnum.class, "One");
Now this use to cause AEnum.class's static fields to be created, so it would populate the registry, but it doesn't.
Thanks again for your quick reply,
Kenny
Stefan Schulz - 14 Feb 2006 07:59 GMT I would strongly suggest not to use such black magic anyway, but yes - this code will no longer trigger a initialization of AEnum (why should it?) and therefore does not populate your map.
However, seriously - could you try and be a little less obscure? Who is supposed to "get" this behaviour if more then these two classes are involved? I consider myself not the cleanest coder around, but this is beyond my pain threshold... and as you could see, error-prone too. Statics should be handled extra carefully, since much obscure behaviour can happen (finals visibly changing value, class init or no class init...)
Chris Uppal - 14 Feb 2006 11:49 GMT > The code is basically an Enum, and a global enum repository. It looks > basically like (I'll cut out what I can): [quoted text clipped - 6 lines] > AEnum("Two")); > // private constructor So you (or the code's original author) are effectively doing reflection "by hand". It might have been better to use reflection explicitly in whatever part of the overall design actually calls for it (you didn't show that bit, and anyway it's not relevant here).
BTW -- just as an aside -- I don't know why Stefan finds this so objectionable. It seems perfectly clear to me. It may not be the best approach to the underlying design problem (or there again it may be the best, who knows ?), but /given/ this approach, I don't see much wrong with the implementation.
-- chris
John C. Bollinger - 15 Feb 2006 03:22 GMT > BTW -- just as an aside -- I don't know why Stefan finds this so objectionable. > It seems perfectly clear to me. It may not be the best approach to the > underlying design problem (or there again it may be the best, who knows ?), but > /given/ this approach, I don't see much wrong with the implementation. I'm with Stefan inasmuch as before JLS3 it was always somewhat vague exactly when a class was to be initialized, therefore Kenny's approach could not be assumed reliable in Java 1.4 or earlier. It depends on an implementation detail of Sun's (I presume) Java implementation; as such, it should not be shocking that a software update broke it.
 Signature John Bollinger jobollin@indiana.edu
Chris Uppal - 15 Feb 2006 09:23 GMT > I'm with Stefan inasmuch as before JLS3 it was always somewhat vague > exactly when a class was to be initialized, "Vague"? The only vagueness in the JLS2 is the same as that I mentioned in the JLS3 (in fact the wording appears to be more or less the same -- I didn't notice any changes anyway). Under the wording of the JLS2 it always has been flat-out illegal for a class literal expression to cause class initialisation. Under the JL3 wording it is similarly illegal.
> therefore Kenny's approach > could not be assumed reliable in Java 1.4 or earlier. It depends on an > implementation detail of Sun's (I presume) Java implementation; as such, > it should not be shocking that a software update broke it. If we take the JLS at face value, then Kenny's (predecessor's) code is not depending on an implementation detail, it is depending on a buggy implementation -- the difference is subtle but real ;-)
But I continue to think that the behaviour required by the JLS is wrong (violates principle of least astonishement for one thing), and that JDK 1.4.x was correct, and JDK 1.5 is in error.
-- chris
Kenny - 15 Feb 2006 15:37 GMT Yes. I plan to move this code to 5.0 syntax as soon as I can, but first all the unit tests have to pass with as little change as possible.
For a little more background on this. I didn't write it, but I know who did, and I know the constraints they were under at the time (no reflection, no singletons, etc).
Kenny
Roedy Green - 13 Feb 2006 22:12 GMT On Mon, 13 Feb 2006 20:00:03 -0000, "Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote, quoted or indirectly quoted someone who said :
>> I'm having the problem that classes in java 1.5 do not seem to run >> their static elements until the class is first used. this sounds like "if a tree falls in the forest, when there is no one to hear it, does it make a sound" sort of questions.
In what sort of instance does it make a difference precisely when the statics are initialised? Is this a matter of where you catch exceptions in static init code?
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Chris Smith - 15 Feb 2006 15:50 GMT > On Mon, 13 Feb 2006 20:00:03 -0000, "Chris Uppal" > <chris.uppal@metagnostic.REMOVE-THIS.org> wrote, quoted or indirectly [quoted text clipped - 9 lines] > statics are initialised? Is this a matter of where you catch > exceptions in static init code? It makes a difference when the static initializer for a class has a side-effect outside of the scope of the class. For example, it was originally considered acceptable to load a JDBC driver by using a Class literal... but it's now known that you have to use the longer overload of Class.forName or call newInstance() to create an object of the driver in order to ensure that the class is loaded.
 Signature www.designacourse.com The Easiest Way To Train Anyone... Anywhere.
Chris Smith - Lead Software Developer/Technical Trainer MindIQ Corporation
Adam Maass - 14 Feb 2006 03:07 GMT > Hello, > [quoted text clipped - 14 lines] > class initalize itself without having to know what type of class it is. > Can anyone offer any suggestions? The difference in behavior you've noted is allowed by the language and platform specifications, and has been allowed for several generations of JVMs. It just so happens that the 1.4 Sun VMs did the "class initialization" on load, rather than waiting for "first use."
-- Adam Maass
Chris Uppal - 14 Feb 2006 11:38 GMT > The difference in behavior you've noted is allowed by the language and > platform specifications, and has been allowed for several generations of > JVMs. It just so happens that the 1.4 Sun VMs did the "class > initialization" on load, rather than waiting for "first use." I don't think that's true. Assuming that the wording in the JLS is deliberate, the 1.4 series JVMs were all seriously buggy in this respect. The JLS does not leave it /open/ whether initialisation happens in this case -- it clearly[*] states that initialisation is /not/ permitted. No "difference in behavior" is "allowed by the language and platform specifications".
As I have said, I believe that the JLS itself is at fault here, and thus that the 1.5 series JVMs are /also/ at fault, wheras the 1.4 series were correct. I'm willing to be persuaded otherwise, but that's my current opinion. Whatever the truth of the matter, the /change/ in behaviour is not a good thing.
( [*] There's some fudged wording in the "spec":
Invocation of certain reflective methods in class Class and in package java.lang.reflect also causes class or interface initialization.
"certain reflective methods" -- great! That /really/ ties down the specification... Anyway, neither of the cases mentioned apply here, so we can rely on the next sentence:
A class or interface will not be initialized under any other circumstance. )
-- chris
Adam Maass - 15 Feb 2006 07:04 GMT >> The difference in behavior you've noted is allowed by the language and >> platform specifications, and has been allowed for several generations of [quoted text clipped - 10 lines] > behavior" is > "allowed by the language and platform specifications". I believe the only guarantee the specs made was that a class is initialized before first use -- they make no guarantee of when that initialization actually happens; some VMs do it on class load, others do it just before first use. (In this respect, it's similar to the loose guarantee about the timing of garbage collection.) I'm too loopy at the moment to read the specifications so I can quote, but I will get to it soon.
-- Adam Maass
Chris Uppal - 15 Feb 2006 09:29 GMT > I believe the only guarantee the specs made was that a class is > initialized before first use -- they make no guarantee of when that [quoted text clipped - 3 lines] > moment to read the specifications so I can quote, but I will get to it > soon. When the loopyness wears off, you'll find that initialisation is specified to happen on first use (for a specified meaning of "use") and that it is specifically /not/ allowed to happen earlier. Many (all?) of the prior phases of class loading /are/ allowed to happen eagerly, it is only the execution of <cinit> that has a fixed temporal semantics.
It's in section 12.4.1.
-- chris
Roedy Green - 15 Feb 2006 12:05 GMT On Wed, 15 Feb 2006 09:29:27 -0000, "Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote, quoted or indirectly quoted someone who said :
>When the loopyness wears off, you'll find that initialisation is specified to >happen on first use (for a specified meaning of "use") and that it is >specifically /not/ allowed to happen earlier. Many (all?) of the prior phases >of class loading /are/ allowed to happen eagerly, it is only the execution of ><cinit> that has a fixed temporal semantics. you want to allow the compiler writer as much latitude as possible to optimise, e.g. preemptively load classes it remember you used last time -- but it can't run their initalisers until you actually DO use them.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Adam Maass - 17 Feb 2006 05:03 GMT >> I believe the only guarantee the specs made was that a class is >> initialized before first use -- they make no guarantee of when that [quoted text clipped - 14 lines] > > It's in section 12.4.1. Of both the second and third editions of the spec. There is the noted fudge factor about "some reflective methods will cause initialization." (Paraphrase.) Evaluation of the the class literal apparently was in that set in 1.4.x but is no longer in that set as of 1.5.
Bleh. I wish the spec weren't so vague on this point.
-- Adam Maass
Kenny - 15 Feb 2006 15:46 GMT It seems the change was at least documented. I came across: http://java.sun.com/j2se/1.5.0/compatibility.html
Under: Incompatibilities in the Java 2 Platform Standard Edition 5.0 (since 1.4.2) #5 "Previously, evaluating a class literal (for example, Foo.class) caused the class to be initialized; as of 5.0, it does not. Code that depends on the previous behavior should be rewritten."
I agree as well, the change doesn't seem like a good thing. Hopefully it was done for a good reason. The forName() works great for the moment, and I'll rewrite that section after all the tests pass.
Kenny
Chris Uppal - 16 Feb 2006 10:37 GMT > It seems the change was at least documented. I came across: > http://java.sun.com/j2se/1.5.0/compatibility.html Aha. Then at least it's deliberate -- which is something...
Thanks for the pointer.
-- chris
Adam Maass - 18 Feb 2006 05:59 GMT > Hello, > [quoted text clipped - 18 lines] > > Kenny For what it's worth, I consider non-trivial class initialization a code smell. There are too many things that can go wrong with it. Among them:
* What happens if class initialization throws exceptions? * Class initialization often happens far earlier than I usually, naively, expect it to. * And, as we've seen, the actual behavior of when class initialization happens isn't quite predictable.
-- Adam Maass
Stefan Schulz - 19 Feb 2006 02:10 GMT > For what it's worth, I consider non-trivial class initialization a code > smell. There are too many things that can go wrong with it. Among them: > > * What happens if class initialization throws exceptions? An ExceptionInInitializerError gets thrown
> * Class initialization often happens far earlier than I usually, naively, > expect it to. Which is why i would try and keep its effects strictly contained within that one class. How, and when the class performs its internal housekeeping is its own business, but i would strongly suggest not mucking around too much.
> * And, as we've seen, the actual behavior of when class initialization > happens isn't quite predictable. It probably is, however it will depend on the bytecode instead of the source code, and therefore is highly arcane, and not something you should rely on.
Adam Maass - 19 Feb 2006 20:34 GMT >> For what it's worth, I consider non-trivial class initialization a code >> smell. There are too many things that can go wrong with it. Among them: >> >> * What happens if class initialization throws exceptions? > > An ExceptionInInitializerError gets thrown That's the immediate effect, yes, but I've seen that Error logged to a log file and the rest of system just go blithely on until some other Throwable gets raised -- typically a NoClassDefFoundError or some such. Those are blazingly hard to debug.
-- Adam Maass
Stefan Schulz - 19 Feb 2006 21:57 GMT > >> For what it's worth, I consider non-trivial class initialization a code > >> smell. There are too many things that can go wrong with it. Among them: [quoted text clipped - 7 lines] > gets raised -- typically a NoClassDefFoundError or some such. Those are > blazingly hard to debug. Those should not happen. Anyone just logging and discarding an Error should be drawn and quartered. An Error means something went so majorly wrong that the system can't be expected to recover. Okay, so sometimes this might not be true, and sometimes, it might be possibly to at least fail gracefully. But just discarding one is about the worst thing you can do.
Adam Maass - 20 Feb 2006 06:52 GMT >> >> For what it's worth, I consider non-trivial class initialization a >> >> code [quoted text clipped - 18 lines] > fail gracefully. But just discarding one is about the worst thing you > can do. This is Tomcat, at least in some of its 4.1.x incarnations.
The problem is partly due to the notion of multiple webapps residing in the same servlet container -- if one of them raises ExceptionInInitializerError, that one webapp is bad, but does not imply that the whole JVM instance is bad. Therefore, the JVM itself does not shut down. Webapps (can) have multiple initialization hooks, so if one part of it raises ExceptionInInitializerError, in principle, the other parts might still successfully initialize and run. Of course, that generally isn't the case...
I wonder if the Tomcat 5 series have addressed this any better... I'll have to go check....
-- Adam Maass
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|