javajvmclassloaderbytecodestatic-initializer

Loading, Linking, and Initializing - When does a class get loaded?


My understanding of classloading was that a class gets loaded when it is first needed (to put it in a very simple way). Running the following sample with -verbose:class and a modified version of the Iterators class that prints a message when its clinit is called I observed something that I can not really explain though:

public class IteratorsTest
{
    public static void main(String[] args)
    {
        com.google.common.collect.Iterators.forArray(1, 2, 3);
    }
}

The (cleaned-up) output is the following:

[Loaded com.google.common.collect.Iterators from file:...]
[Loaded com.google.common.collect.Iterators$1 from file:...]
---------> Iterators <clinit>

Why is Iterators$1 loaded before clinit is called? It is only defined in the clinit, isn't it?

  static final UnmodifiableListIterator<Object> EMPTY_LIST_ITERATOR =
      new UnmodifiableListIterator<Object>() {
  ...
  }

Which results in the following byte code:

static <clinit>()V
   L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "---------> Iterators clinit --------------"**
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L1
    NEW com/google/common/collect/Iterators$1
    DUP
    INVOKESPECIAL com/google/common/collect/Iterators$1.<init> ()V
   L2
    PUTSTATIC com/google/common/collect/Iterators.EMPTY_LIST_ITERATOR : Lcom/google/common/collect/UnmodifiableListIterator;

And to confuse me even more I have one more sample (too complex to post here) where the same line of code as in the main above leads to the following output:

[Loaded com.google.common.collect.Iterators from file:...]
---------> Iterators <clinit>
[Loaded com.google.common.collect.Iterators$1 from file:...]

This is actually what I would have expected from the simple test program as well.

I tried to find the answer here https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html , but that didn't really help.


Solution

  • Here the summary of the solution for those who don't want to read through all the comments ;)

    1. The difference in the execution order was caused by one of the launchers having -noverify specified. The verifier may cause additional classes to be loaded as also specified here in the JVM Spec. Whether the class is loaded or not, seems to depend on the type of the field to which the object is assigned. More details here. On the other hand, when started with -noverify, there is no verification and hence the loading of the class only happens at exactly the point where it is first used in the code, which is inside the <clinit>in my case.
    2. There are ways to trace the invocation <clinit> without having to modify the byte code. One way is to use the -XX:+TraceClassInitialization on JDK8. This, however, requires a debug version of the JVM (NOTE: this is not your program started in debug mode but really the VM compiled with debug enabled. A guide for how to build it can be found here). The other way - that only comes with JDK9 though - is to use the new JEP 158: Unified JVM Logging feature and to provide something like the following when starting your program:
      -Xlog:class+load=info,class+init=info:file=trace.log (see here for how to get a complete list of tags and arguments)