查看: 101|回复: 0

JVM源码分析-类加载场景实例分析

[复制链接]
发表于 2020-2-16 03:51:57 | 显示全部楼层 |阅读模式
A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个故意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?
场景如下:
  1. public class Main {    static {        System.out.println("Main static block");    }    public static void main(String[] args) {        Helper.staticMethod();    }}public class Helper {    static {        System.out.println("Helper static block");    }    public static void staticMethod() {        System.out.println("Helper#staticMethod");    }    public void test(XXXManager ab, XXXSubInterface xxxSubInterface) {        ab.setXXX(xxxSubInterface);    }}public interface XXX {}public interface XXXSubInterface extends XXX {}public interface XXXManager {    void setXXX(XXX xxx);}
复制代码
添加JVM -varbose参数进行执行,输出是:
  1. [Loaded Main from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]Main static block[Loaded Helper from file:/Users/mazhibin/project/java/loadclasstest/target/classes/][Loaded XXX from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]Helper static blockHelper#staticMethod
复制代码
main方法执行Helper.staticMethod(),而staticMethod方法内里只有打印语句,以是理论上应该只要加载Helper就够了,为了什么会加载到XXX类,好,即使接受可以加载类的情况,为什么是XXX,而不是直接使用到的XXXManager大概XXXSubInterface。你提的题目大概是这个场景。
在说探索过程之前先说下终极结论:在验证Helper类时,校验到setXXX方法,会验证XXXSubInterface类型是否可以赋值到XXX类型,这个时间就会去加载XXX类,然后因为XXX是一个接口,代码中认为接口和Object类是一样的,什么类型都可以赋值给接口类型,以是就直接校验成功,就没有去加载XXXSubInterface类了。
然后在先容一下类加载的过程。首先要清楚一点,“类加载”和“加载”是两个概念,“加载”是“类加载”(Class loading)的一个步骤。类加载包含加载、链接、初始化这三个步骤,此中链接又分为验证、准备、解析这三个子步骤。加载是根据特定名称查找类或接口类型的二进制表示(Binary Representation),并由此二进制表示创建类或接口的过程。链接是为了让类或接口可以被 Java 虚拟机执行,而将类或接口并入虚拟机运行时状态的过程。类或接口的初始化是指执行类或接口的初始化方法。

类加载复杂就复杂在这些步骤执行的机会,而且此中的子步骤还不一定按次序执行,加载、验证、准备、初始化和卸载这5个阶段的次序是固定的,必要按这个次序开始(答应交叉),而解析则不一定,有可能在初始化之后才进行。
那什么时间会开始加载步骤?Java虚拟机规范没有逼迫要求,但是对于初始化阶段,则明确规定了5种情况必要对类进行初始化,分别是:

  • 在执行下列必要引用类或接口的Java虚拟机指令时:new,getstatic,putstatic或invokestatic。这些指令通过字段或方法引用来直接或间接地引用其它类。执行上面所述的new指令,在类或接口没有被初始化过时就初始化它。执行上面的getstatic,putstatic或invokestatic指令时,那些解析好的字段或方法中的类或接口如果还没有被初始化那就初始化它。
  • 在初次调用java.lang.invoke.MethodHandle实例时,它的执行结果为通过Java虚拟机解析出类型是2(REF_getStatic)、4(REF_putStatic)大概6(REF_invokeStatic)的方法句柄(§5.4.3.5)。
  • 在调用JDK核心类库中的反射方法时,例如,Class类或java.lang.reflect包。
  • 在对于类的某个子类的初始化时。
  • 在它被选定为Java虚拟机启动时的初始类(§5.2)时。
结合上面说的,加载、验证、准备、初始化和卸载这5个阶段的次序是固定的,必要按这个次序开始(答应交叉),我们确定了初始化的机会,那么在初始化时大概之前,就要开始加载了。同时还有一点,也就是这个题目涉及到的场景,一个类在验证这个步骤时,会验证A类的字节码,此中可能会涉及到以是来的其他类,根据验证的具体需求,可能必要加载其他类。而这个题目具体的校验过程就是一个方法调用,涉及到类型转换赋值(传入子接口类型,必要转为父接口类型),这种情况下必要加载类型来判定是否可以进行赋值,按理是必要加载赋值左右两边的类型的,但是因为左边类型是接口,被认为都可以赋值,以是没有加载右边类型。
总结来说可能的加载机会为以下几点(不一定全面,是我目前已知的):

  • JVM启动时会预加载一些核心类,比如Object、String等
  • 这个类被第一次真正使用到时,比如主类因为要调用其main方法,天然必要被加载
  • 在A类校验阶段,可能必要加载其代码中使用到的B类
  • 在A类进行某个符号引用的解析时,必要加载对应的B类。比如正在执行A类的一个方法,执行到B.func(),那就必要解析B和func符号引用为直接引用,那天然必要加载B类到方法区中
接下来说下是怎样得到上述结论的,首先类加载的流程是Java虚拟机规范中有写的,可以看看。而具体为什么只加载了XXX类,则要调试JVM源码才能知道了。最近因为有看JVM源码,以是编译了并可以进行GDB调试,然后添加条件断点:break SystemDictionary::load_instance_class if strncmp(class_name._body, "XXX", 3) == 0,表示在加载XXX类的时间停下来,接着分析调用堆栈:
  1. // 加载Main类[Loaded Main from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]// 执行Main的初始化方法Main static block// 因为要执行Helper.staticMethod()语句,触发加载Helper流程[Loaded Helper from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]// 接着断点停在了加载XXX接口的函数调用上Breakpoint 1, SystemDictionary::load_instance_class (class_name=0x7ffff01a5338, class_loader=..., __the_thread__=    0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:13451345    instanceKlassHandle nh = instanceKlassHandle(); // null Handle// 检察函数调用栈,分析为什么会必要加载XXX类(要从下往上看)(gdb) bt#0  SystemDictionary::load_instance_class (class_name=0x7ffff01a5338, class_loader=...,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345#1  0x00007ffff7578062 in SystemDictionary::resolve_instance_class_or_null (name=0x7ffff01a5338,    class_loader=..., protection_domain=..., __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:755#2  0x00007ffff7576a17 in SystemDictionary::resolve_or_null (class_name=0x7ffff01a5338, class_loader=...,    protection_domain=..., __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:203#3  0x00007ffff75765ad in SystemDictionary::resolve_or_fail (class_name=0x7ffff01a5338, class_loader=...,    protection_domain=..., throw_error=true, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:145// 上面就开始了加载流程了// 下文分析了这里是在校验XXXSubInterface类型是否可以赋值到XXX// 下文分析了为什么必要加载XXX接口,而不必要加载XXXSubInterface接口#4  0x00007ffff75df854 in VerificationType::is_reference_assignable_from (this=0x7ffff7fe5770, from=..., context=    0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.cpp:62#5  0x00007ffff753bc37 in VerificationType::is_assignable_from (this=0x7ffff7fe5770, from=...,    context=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.hpp:289#6  0x00007ffff75eba80 in StackMapFrame::pop_stack (this=0x7ffff7fe5e20, type=..., __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/stackMapFrame.hpp:181#7  0x00007ffff75ea155 in ClassVerifier::verify_invoke_instructions (this=0x7ffff7fe60e0, bcs=0x7ffff7fe5dc0,    code_length=18, current_frame=0x7ffff7fe5e20, this_uninit=0x7ffff7fe5f1f, return_type=..., cp=...,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:2064// 下文分析了这里是因为验证Helper.test(LXXXManager;)V这个方法导致的加载XXX接口#8  0x00007ffff75e64ea in ClassVerifier::verify_method (this=0x7ffff7fe60e0, m=...,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:1237#9  0x00007ffff75e0e75 in ClassVerifier::verify_class (this=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:312#10 0x00007ffff75e04b1 in Verifier::verify (klass=..., mode=Verifier::ThrowException, should_verify_class=true,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:127#11 0x00007ffff71f5d8c in instanceKlass::verify_code (this_oop=..., throw_verifyerror=true,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:214// 上面的方法是验证的过程,也就是校验字节码是否正确,是否合法#12 0x00007ffff71f6425 in instanceKlass::link_class_impl (this_oop=..., throw_verifyerror=true,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:321#13 0x00007ffff71f5e73 in instanceKlass::link_class (this=0xfb01ab80, __the_thread__=0x7ffff0028000)// 在类或接口被初始化之前,它必须被链接过,也就是颠末验证、准备阶段,且有可能已经被解析完成了。以是上面是链接的流程    at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:230#14 0x00007ffff71f691f in instanceKlass::initialize_impl (this_oop=..., __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:397#15 0x00007ffff71f5cca in instanceKlass::initialize (this=0xfb01ab80, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:199#16 0x00007ffff7383903 in LinkResolver::resolve_static_call (result=..., resolved_klass=...,    method_name=0x7ffff01a4908, method_signature=0x7ffff0051f28, current_klass=..., check_access=true,    initialize_class=true, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:629// Main类在运行字节码时,执行到invokestatic指令,对应的语句是Helper.staticMethod()// JVM规范中阐明,在执行new,getstatic,putstatic或invokestatic这些指令时,必要确保目标类已经进行初始化流程// 而初始化流程必要确保目标类已经被加载、验证、准备,以是上面会走到Helper的加载、验证、准备的流程// 这个堆栈跟踪到的是验证的流程#17 0x00007ffff738599f in LinkResolver::resolve_invokestatic (result=..., pool=..., index=65537,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1077#18 0x00007ffff738575c in LinkResolver::resolve_invoke (result=..., recv=..., pool=..., index=65537,    byte=Bytecodes::_invokestatic, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1050#19 0x00007ffff7239c58 in InterpreterRuntime::resolve_invoke (thread=0x7ffff0028000,    bytecode=Bytecodes::_invokestatic)    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:686// 我们到第16号栈帧中,可以看出简直是正要执行Helper.staticMethod()方法(gdb) f 16#16 0x00007ffff7383903 in LinkResolver::resolve_static_call (result=..., resolved_klass=...,    method_name=0x7ffff01a4908, method_signature=0x7ffff0051f28, current_klass=..., check_access=true,    initialize_class=true, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:629629       resolved_klass->initialize(CHECK);(gdb) p Klass::cast(current_klass.obj())->external_name()$1 = 0x7fffcc002548 "Main"(gdb) p *[email protected]_name._length$5 = "staticMethod"// 我们到第8号栈帧中,可以看出是因为验证Helper.test(LXXXManager;)V这个方法导致的加载XXX接口(gdb) f 8#8  0x00007ffff75e64ea in ClassVerifier::verify_method (this=0x7ffff7fe60e0, m=...,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:12371237                &this_uninit, return_type, cp, CHECK_VERIFY(this));(gdb) p m->name_and_sig_as_C_string()$6 = 0x7fffcc002568 "Helper.test(LXXXManager;)V"// 我们到第4号栈帧中,可以看出是在校验XXXSubInterface类型是否可以赋值到XXX(gdb) f 4#4  0x00007ffff75df854 in VerificationType::is_reference_assignable_from (this=0x7ffff7fe5770, from=...,    context=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.cpp:6262          Handle(THREAD, klass->protection_domain()), true, CHECK_false);(gdb) p *from.name()[email protected]()._length$10 = "XXXSubInterface"(gdb) p *name()[email protected]()._length$11 = "XXX"
复制代码
上面分析出了加载是因为验证的流程,具体触发加载的验证代码如下,是验证赋值操作是否可以成功的:
  1. // hotspot/src/share/vm/classfile/verificationType.cppbool VerificationType::is_reference_assignable_from(    const VerificationType& from, ClassVerifier* context, TRAPS) const {  instanceKlassHandle klass = context->current_class();  if (from.is_null()) {    // null is assignable to any reference    return true;  } else if (is_null()) {    return false;  } else if (name() == from.name()) {    return true;  } else if (is_object()) {    // 如果赋值语句左边类型是对象,判定是否是Object,如果是那都可以赋值成功,返回true    // We need check the class hierarchy to check assignability    if (name() == vmSymbols::java_lang_Object()) {      // any object or array is assignable to java.lang.Object      return true;    }    // 否则必要把左边类型加载进来 class_loader()),        Handle(THREAD, klass->protection_domain()), true, CHECK_false);    KlassHandle this_class(THREAD, obj);    // 如果左边类型是接口    if (this_class->is_interface()) {      // 这里注释阐明了,认为接口和Object一样,都可以赋值成功以是返回true      // We treat interfaces as java.lang.Object, including      // java.lang.Cloneable and java.io.Serializable      return true;    } else if (from.is_object()) {      // 否则要把赋值赋予右边的类型也加载进来      klassOop from_class = SystemDictionary::resolve_or_fail(          from.name(), Handle(THREAD, klass->class_loader()),          Handle(THREAD, klass->protection_domain()), true, CHECK_false);      return instanceKlass::cast(from_class)->is_subclass_of(this_class());    }  } else if (is_array() && from.is_array()) {    VerificationType comp_this = get_component(context, CHECK_false);    VerificationType comp_from = from.get_component(context, CHECK_false);    if (!comp_this.is_bogus() && !comp_from.is_bogus()) {      return comp_this.is_assignable_from(comp_from, context, CHECK_false);    }  }  return false;}
复制代码
如许就分析完了,尝试把XXX和XXXSubInterface改成class,可以发现两个都会被加载,符合上面这个代码的逻辑。
接着顺便分析一下Helper类加载的堆栈:
  1. // 加载Main类[Loaded Main from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]// 执行Main初始化方法Main static block// 断点停在加载Helper类的逻辑上Breakpoint 2, SystemDictionary::load_instance_class (class_name=0x7ffff01a60d8, class_loader=..., __the_thread__=    0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:13451345      instanceKlassHandle nh = instanceKlassHandle(); // null Handle(gdb) bt#0  SystemDictionary::load_instance_class (class_name=0x7ffff01a60d8, class_loader=...,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345#1  0x00007ffff7578062 in SystemDictionary::resolve_instance_class_or_null (name=0x7ffff01a60d8,    class_loader=..., protection_domain=..., __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:755#2  0x00007ffff7576a17 in SystemDictionary::resolve_or_null (class_name=0x7ffff01a60d8, class_loader=...,    protection_domain=..., __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:203#3  0x00007ffff75765ad in SystemDictionary::resolve_or_fail (class_name=0x7ffff01a60d8, class_loader=...,    protection_domain=..., throw_error=true, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:145#4  0x00007ffff70c1915 in constantPoolOopDesc::klass_at_impl (this_oop=..., which=18,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp:102#5  0x00007ffff6fa1f69 in constantPoolOopDesc::klass_at (this=0xfb019e90, which=18,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/constantPoolOop.hpp:366#6  0x00007ffff70c2c84 in constantPoolOopDesc::klass_ref_at (this=0xfb019e90, which=65537,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp:382#7  0x00007ffff73817c0 in LinkResolver::resolve_klass (result=..., pool=..., index=65537,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:161#8  0x00007ffff7385871 in LinkResolver::resolve_pool (resolved_klass=..., [email protected]: 0x0,    [email protected]: 0x0, current_klass=..., pool=..., index=65537,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1062// Main类在运行字节码时,执行到invokestatic指令,对应的语句是Helper.staticMethod()// JVM规范中阐明,指令anewarray、checkcast、getfield、getstatic、instanceof、nvokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic将符号引用指向运行时常量池,执行上述任何一条指令都必要对它的符号引用的进行解析。// 以是这里必要将Main中的运行时常量池中的Helper和staticMethod进行符号解析// 符号解析是把符号引用替换为真实引用,天然必要加载Helper类,才能进行替换,以是上面就触发了Helper的加载流程#9  0x00007ffff738595b in LinkResolver::resolve_invokestatic (result=..., pool=..., index=65537,    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1076#10 0x00007ffff738575c in LinkResolver::resolve_invoke (result=..., recv=..., pool=..., index=65537,    byte=Bytecodes::_invokestatic, __the_thread__=0x7ffff0028000)    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1050#11 0x00007ffff7239c58 in InterpreterRuntime::resolve_invoke (thread=0x7ffff0028000,---Type  to continue, or q  to quit---    bytecode=Bytecodes::_invokestatic)    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:686
复制代码
[code]// hotspot/src/share/vm/interpreter/linkResolver.cppvoid LinkResolver::resolve_invokestatic(CallInfo& result, constantPoolHandle pool, int index, TRAPS) {  KlassHandle  resolved_klass;  Symbol* method_name = NULL;  Symbol* method_signature = NULL;  KlassHandle  current_klass;  // 解析常量池中的符号引用,会触发加载被调用类的流程

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?用户注册

x

相关技术服务需求,请联系管理员和客服QQ:2753533861或QQ:619920289
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

帖子推荐:
客服咨询

QQ:2753533861

服务时间 9:00-22:00

快速回复 返回顶部 返回列表