Java 5 classloader not making static calls.

K

Kenny

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
 
C

Chris Uppal

Kenny 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.
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
 
K

Kenny

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
 
R

Roedy Green

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?
 
A

Adam Maass

Kenny said:
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?

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
 
S

Stefan Schulz

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...)
 
C

Chris Uppal

Adam said:
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
 
C

Chris Uppal

Kenny said:
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

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
 
J

John C. Bollinger

Chris said:
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.
 
A

Adam Maass

Chris Uppal" said:
Adam said:
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".

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
 
C

Chris Uppal

John said:
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
 
C

Chris Uppal

Adam said:
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.

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
 
R

Roedy Green

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.
 
K

Kenny

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
 
K

Kenny

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
 
C

Chris Smith

Roedy Green said:
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?

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.

--
www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 
A

Adam Maass

Chris Uppal 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.

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
 
A

Adam Maass

Kenny said:
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

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
 
S

Stefan Schulz

Adam said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top