2017-01-15 51 views
5

我对类加载的理解是,一个类在第一次需要时被加载(以非常简单的方式)。运行与-verbose下面的示例:类和打印时,其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$1 from file:...] 
---------> Iterators <clinit> 

为什么在调用clinit之前加载了Iterator $ 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$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; 

而迷惑我更我多了一个样本(太复杂,张贴在这里),其中相同的代码行作为主以上导致以下输出:

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

这实际上是我从简单的测试程序以及所期望的。

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

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

openjdk9的新日志记录基础结构允许对很多内部进行相当细致的分解,包括类加载和初始化。也许这会提供你想要的信息。 – the8472

+0

好主意,我读了关于新的日志功能,但还没有尝试过。也许值得做一个测试运行。谢谢! –

+0

尽管我发现它,但我想知道JDK9是否会有所帮助,而且事实上,这可以完成这项工作。我开始使用-Xlog:class + init = info,class + load = info:file = trace.log(btw:是否有可用选择器的完整列表?http://openjdk.java.net/ jeps/158是不够的)。 PS:它实际上是你在这里的评论http://stackoverflow.com/questions/39321345/how-do-i-measure-jvm-startup-time我在这里读到第一。 –

回答

3

这里为那些不想通过所有评论读谁的解决方案的概要;)

  1. 执行顺序的差异是由指定了-noverify的发射器之一引起的。验证者可能会导致额外的类加载,这也在JVM Spec中指定。该类是否被加载,似乎取决于该对象所分配的字段的类型。更多详情here。另一方面,当从-noverify开始时,没有验证,因此该类的加载只发生在代码中第一次使用的地方,在我的情况下,该代码位于<clinit>的内部。
  2. 有办法跟踪调用<clinit>而不必修改字节码。一种方法是在JDK8上使用-XX:+TraceClassInitialization。但是,这需要调试版本的JVM(注意:这不是您的程序是以调试模式启动的,而是真正在调试启用的情况下编译的虚拟机。关于如何构建它的指南可以参见here)。另一种方法 - 即只配备了JDK9虽然 - 是使用新JEP 158: Unified JVM Logging feature,并提供类似的启动程序时以下几点:
    -Xlog:class+load=info,class+init=info:file=trace.log(见here如何获得的标签和参数的完整列表)
5

可能是什么,有时被首先执行clinit的原因,有时匿名类被首先加载?

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

  • 装载
    • 验证
    • 准备
    • 分辨率
  • 初始化
  • 使用
  • 卸载

,现在我们专注于分辨率分辨率相初始化阶段 的参考类装走位和<克林特>发生在初始化阶段。 装载verfication准备初始化卸载的顺序是固定的,但是时候调用分辨率阶段是不固定的,它可能发生之前初始化(对应你的前一种情况)阶段,它也可能发生之后初始化在某些情况下(对应您的后一种情况)。

对于性能,HotSpot虚拟机通常会等到类初始化加载并链接一个类。因此,如果A类引用类B,则加载类A不一定会导致B类加载(除非需要进行验证)。执行引用B的第一条指令将导致B的初始化,这需要加载和链接类B.

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

我不知道是否存在一些JVM参数,你可以得到的时候JVM直接调用< clinit>方法,但还有另一种方式,你可以做到这一点,利用jvm_ti。您可以听methodEntry之类的事件,然后获取调用< clinit>方法的时间。欲了解更多信息谷歌jvm_ti

参考

+0

我也使用-XX:+ TraceClassResolution运行,但在加载类之前没有Iterators $ 1的输出。是什么让你认为决议的时间不固定?根据我的理解,JVM规范第5.4.3章中的指示可以解决符号引用问题。然而,这些指令仅在clinit期间被调用(因此我期望在该点处的分辨率和负载)。我真的需要了解在调用Iterator的clinit之前触发加载Iterator $ 1的是什么。 –

+1

@Haasip Satang [jvm规范5.4链接](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.4.3)说:'Java虚拟机器实现可以选择在分类或接口中分别使用每个符号引用(“懒惰”或“迟到”分辨率),或者在类正在验证时一次全部解析它们(“渴望”或“静态” “的解决方案)。 –

+0

是的,但由于我们谈论的是同一个虚拟机(没有不同的实现),我会假设相同的行为。但即使JVM决定为同一行代码选择不同的方法,我想理解它的原因。这肯定不是随机的。它总是首先在程序A中加载类,而在B clinit中被首先调用。一行代码的方法在两个程序中都完全相同。问题是为什么JVM选择不同的策略。注意:我正在对JVM中的非确定性进行一些研究,这就是为什么我需要知道这个低级细节。 –