JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
创新互联专注于大连企业网站建设,自适应网站建设,电子商务商城网站建设。大连网站建设公司,为大连等地区提供建站服务。全流程按需设计网站,专业设计,全程项目跟踪,创新互联专业和态度为您提供的服务
本篇就给大家分析类加载场景,希望能对你有所帮助。
A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?。
场景如下:
- 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参数进行执行,输出是:
- [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 block
- Helper#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种情况需要对类进行初始化,分别是:
结合上面说的,加载、验证、准备、初始化和卸载这5个阶段的顺序是固定的,需要按这个顺序开始(允许交叉),我们确定了初始化的时机,那么在初始化时或者之前,就要开始加载了。同时还有一点,也就是这个问题涉及到的场景,一个类在验证这个步骤时,会验证A类的字节码,其中可能会涉及到所以来的其他类,根据验证的具体需求,可能需要加载其他类。而这个问题具体的校验过程就是一个方法调用,涉及到类型转换赋值(传入子接口类型,需要转为父接口类型),这种情况下需要加载类型来判断是否可以进行赋值,按理是需要加载赋值左右两边的类型的,但是因为左边类型是接口,被认为都可以赋值,所以没有加载右边类型。
总结来说可能的加载时机为以下几点(不一定全面,是我目前已知的):
接下来说下是如何得到上述结论的,首先类加载的流程是Java虚拟机规范中有写的,可以看看。而具体为什么只加载了XXX类,则要调试JVM源码才能知道了。最近因为有看JVM源码,所以编译了并可以进行GDB调试,然后添加条件断点:break SystemDictionary::load_instance_class if strncmp(class_name._body, "XXX", 3) == 0,表示在加载XXX类的时候停下来,接着分析调用堆栈:
- / 加载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:1345
- 1345 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:629
- 629 resolved_klass->initialize(CHECK);
- (gdb) p Klass::cast(current_klass.obj())->external_name()
- $1 = 0x7fffcc002548 "Main"
- (gdb) p *method_name._body@method_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:1237
- 1237 &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:62
- 62 Handle(THREAD, klass->protection_domain()), true, CHECK_false);
- (gdb) p *from.name()._body@from.name()._length
- $10 = "XXXSubInterface"
- (gdb) p *name()._body@name()._length
- $11 = "XXX"
上面分析出了加载是因为验证的流程,具体触发加载的验证代码如下,是验证赋值操作是否可以成功的:
- // hotspot/src/share/vm/classfile/verificationType.cpp
- bool 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;
- }
- // 否则需要把左边类型加载进来 <=========================== 加载行为发生在这里
- klassOop obj = SystemDictionary::resolve_or_fail(
- name(), Handle(THREAD, klass->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类加载的堆栈:
- // 加载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:1345
- 1345 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=..., method_name=@0x7ffff7fe6638: 0x0,
- method_signature=@0x7ffff7fe6630: 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
- // hotspot/src/share/vm/interpreter/linkResolver.cpp
- void LinkResolver::resolve_invokestatic(CallInfo& result, constantPoolHandle pool, int index, TRAPS) {
- KlassHandle resolved_klass;
- Symbol* method_name = NULL;
- Symbol* method_signature = NULL;
- KlassHandle current_klass;
- // 解析常量池中的符号引用,会触发加载被调用类的流程 <==================
- resolve_pool(resolved_klass, method_name, method_signature, current_klass, pool, index, CHECK);
- // 解析从方法签名解析出方法oop,会触发类的初始化流程 <==================
- resolve_static_call(result, resolved_klass, method_name, method_signature, current_klass, true, true, CHECK);
- }
总结
总结来说可能的加载时机为以下几点(不一定全面,是我目前已知的):
对应A类调用B类的情况,JVM规范中说明,指令anewarray、checkcast、getfield、getstatic、instanceof、nvokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic将符号引用指向运行时常量池,执行上述任何一条指令都需要对它的符号引用的进行解析,所以需要解析A类中对B类的符号引用,而解析是把符号引用替换为真实引用,所以需要把B类加载到方法区中,这就触发了加载流程。
而B类的链接流程,则是因为JVM规范中说明,在执行new,getstatic,putstatic或invokestatic这些指令时,需要确保目标类已经进行初始化流程,而初始化流程需要确保目标类已经被加载、验证、准备,而加载之前执行过了,所以需要进入验证和准备的流程。而链接中的解析过程不会执行,B类的解析会在执行B类中相关代码时再进行。
上面说的两个过程都是在执行字节码时触发的,比如invokestaic。而B类在验证的过程中,可能又会需要加载其代码中使用到的C类。
本文题目:JVM(JavaVirtualMachine)源码分析-类加载场景实例分析
本文地址:http://www.shufengxianlan.com/qtweb/news36/490636.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联