加载、链接和初始化 - class 何时加载?

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

我对 class 加载的理解是 class 在第一次需要时加载(简单来说)。 运行 以下示例带有 -verbose:class 和迭代器的修改版本 class 在调用 clinit 时打印一条消息 我观察到一些我无法真正解释的东西:

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

(清理后的)输出如下:

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

为什么在调用 clinit 之前加载 Iterators$1?它只在 clinit 中定义,不是吗?

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

这导致以下字节代码:

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
    DUP
    INVOKESPECIAL com/google/common/collect/Iterators.<init> ()V
   L2
    PUTSTATIC com/google/common/collect/Iterators.EMPTY_LIST_ITERATOR : Lcom/google/common/collect/UnmodifiableListIterator;

更让我困惑的是,我还有一个示例(对于 post 这里来说太复杂了),其中与上面主要部分相同的代码行导致以下输出:

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

这实际上也是我对简单测试程序的期望。

我试图在这里 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html 找到答案,但这并没有真正帮助。

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

class加载过程包含以下过程。

  • 加载中
  • linking
    • 验证
    • 准备
    • 分辨率
  • 初始化
  • 使用
  • 卸载

现在我们关注的是 resolutioninitialization 阶段 引用class加载发生在解决阶段发生在初始化[=65] =]阶段。 loading verfication preparation initialization unloading的顺序是固定的,但是调用resolution阶段的时间不固定,可能发生在[=31之前=]初始化(对应你之前的情况)阶段,在某些情况下(对应你后面的情况)也可能发生在初始化之后。

为了性能,HotSpot VM 通常等到 class 初始化加载和 link 一个 class。所以如果classA引用classB,加载classA不一定会导致加载classB(除非需要验证)。执行引用 B 的第一条指令将导致 B 的初始化,这需要加载和 linking 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?

我不知道是否有一些jvm参数可以获取jvm直接调用方法的时间,但是有另一种方法可以实现,使用 jvm_ti。您可以监听一些事件,例如 methodEntry,然后获取调用 方法的时间。更多信息googlejvm_ti

参考:

这里是为那些不想通读所有评论的人准备的解决方案摘要 ;)

  1. 执行顺序的不同是由于其中一个启动器指定了 -noverify。验证器可能会导致额外的 classes 被加载,同样在 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 中指定。另一方面,当以 -noverify 开始时,没有验证,因此 class 的加载只发生在代码中首次使用它的位置,它位于 <clinit>在我的例子中。
  2. 有一些方法可以跟踪调用 <clinit> 而无需修改字节码。一种方法是在 JDK8 上使用 -XX:+TraceClassInitialization。然而,这需要 JVM 的调试版本(注意:这不是您的程序以调试模式启动,而是真正启用调试编译的 VM。可以找到有关如何构建它的指南 here). The other way - that only comes with JDK9 though - is to use the new JEP 158: Unified JVM Logging feature 并提供启动程序时类似以下内容:
    -Xlog:class+load=info,class+init=info:file=trace.log(有关如何获取标签和参数的完整列表,请参阅