Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

  • What could be the reason for sometimes the clinit being executed first and sometimes the anonymous class being loaded first?
  • Is there a way to trace when the JVM invokes the clinit of the classes? something similar to -verbose:class or -XX:+TraceClassLoading, etc?
like image 722
Haasip Satang Avatar asked Jan 15 '17 00:01

Haasip Satang


2 Answers

What could be the reason for sometimes the clinit being executed first and sometimes the anonymous class being loaded first?

the class load process contains the follow process.

  • loading
  • linking
    • verification
    • preparation
    • Resolution
  • initialization
  • Using
  • unload

and now what we focus on resolution and initialization phase for the reference class loaded take place at resolution phase and the <clint> take place at initialization phase. The order of loading verfication preparation initialization unloading is fixed, however the time when invoke resolution phase is not fixed, it may take place before initialization(correspond your former case) phase and it may also take place after initialization at some scenario(correspond your latter case).

For performance, the HotSpot VM generally waits until class initialization to load and link a class. So if class A references class B, loading class A will not necessarily cause loading of class B (unless required for verification). Execution of the first instruction that references B will cause initialization of B, which requires loading and linking of class B.

Is there a way to trace when the JVM invokes the clinit of the classes? something similar to -verbose:class or -XX:+TraceClassLoading, etc?

I dont know whether there exist some jvm parameter that you can get the time when jvm invokes <clinit> method directly, but there is another way that you can achieve this, using jvm_ti. You can listen some event like methodEntry and then get the time invoking <clinit> method. For more information google jvm_ti.

reference:

  • Chapter 7 of Inside the Java Virtual Machine
  • jvm_ti
  • HotSpot Runtime Overview
like image 140
nail fei Avatar answered Sep 17 '22 12:09

nail fei


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)
like image 39
Haasip Satang Avatar answered Sep 18 '22 12:09

Haasip Satang