加载,链接和初始化 - 何时加载类?

Haa*_*ang 7 java jvm bytecode classloader static-initializer

我对类加载的理解是一个类在第一次需要时被加载(以非常简单的方式放置).使用-verbose:class和Iterators类的修改版本运行以下示例,该类在调用其clinit时打印消息我观察到了一些我无法解释的内容:

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

(清理)输出如下:

[Loaded com.google.common.collect.Iterators from file:...]
[Loaded com.google.common.collect.Iterators$1 from file:...]
---------> Iterators <clinit>
Run Code Online (Sandbox Code Playgroud)

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

  static final UnmodifiableListIterator<Object> EMPTY_LIST_ITERATOR =
      new UnmodifiableListIterator<Object>() {
  ...
  }
Run Code Online (Sandbox Code Playgroud)

这导致以下字节代码:

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;
Run Code Online (Sandbox Code Playgroud)

为了让我更加困惑,我还有一个样本(太复杂了,无法在此处发布),其中与上面主要内容相同的代码行导致以下输出:

[Loaded com.google.common.collect.Iterators from file:...]
---------> Iterators <clinit>
[Loaded com.google.common.collect.Iterators$1 from file:...]
Run Code Online (Sandbox Code Playgroud)

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

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

  • 有时候诊所首先被执行,有时候匿名类首先被加载的原因是什么?
  • 有没有办法跟踪JVM何时调用类的临时?类似于-verbose:class或-XX:+ TraceClassLoading等?

nai*_*fei 7

有时候诊所首先被执行,有时候匿名类首先被加载的原因是什么?

类加载过程包含以下过程.

  • 装载
  • 链接
    • 验证
    • 制备
    • 解析度
  • 初始化
  • 运用
  • 卸下

现在我们关注加载的引用类的分辨率初始化阶段发生在分辨率阶段,<clint>发生在初始化阶段.加载修改准备初始化卸载的顺序是固定的,但是调用解析阶段的时间不固定,它可能发生在初始化之前(对应你的前一种情况)阶段,也可能发生在某些情况下的初始化之后(对应后者)案件).

为了提高性能,HotSpot VM通常会等到类初始化以加载和链接类.因此,如果类A引用类B,则加载类A不一定会导致加载类B(除非验证需要).执行引用B的第一条指令将导致B的初始化,这需要加载和链接B类.

有没有办法跟踪JVM何时调用类的临时?类似于-verbose:class或-XX:+ TraceClassLoading等?

我不知道是否存在一些jvm参数,你可以获得jvm直接调用<clinit>方法的时间,但还有另一种方法可以实现这一点,使用jvm_ti.你可以听一些像methodEntry这样的事件,然后调用<clinit>方法.有关更多信息,请访问google jvm_ti.

参考:


Haa*_*ang 6

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

  1. 执行顺序的差异是由其中一个已-noverify指定的启动程序引起的.验证程序可能会导致加载其他类,如JVM规范中所述.无论是否加载类,似乎都取决于对象所分配的字段的类型.更多细节在这里.另一方面,当开始时-noverify,没有验证,因此类的加载仅发生在代码中首次使用的位置,<clinit>在我的情况下.
  2. 有一些方法可以跟踪调用<clinit>而无需修改字节代码.一种方法是使用-XX:+TraceClassInitializationon JDK8.但是,这需要JVM的调试版本(注意:这不是您的程序在调试模式下启动,但实际上是在启用调试的情况下编译的VM.可以在此处找到有关如何构建它的指南).另一种方式 - 只提供JDK9 - 是使用新的JEP 158:统一JVM日志记录功能,并在启动程序时提供类似以下内容:(
    -Xlog:class+load=info,class+init=info:file=trace.log请参阅此处了解如何获取完整的标记和参数列表)