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

时间:2022-09-27 17:59:32

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 虚拟机执行,而将类或接口并入虚拟机运行时状态的过程。类或接口的初始化是指执行类或接口的初始化方法<clinit>

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

类加载复杂就复杂在这些步骤执行的时机,并且其中的子步骤还不一定按顺序执行,加载、验证、准备、初始化和卸载这5个阶段的顺序是固定的,需要按这个顺序开始(允许交叉),而解析则不一定,有可能在初始化之后才进行。

那什么时候会开始加载步骤?Java虚拟机规范没有强制要求,但是对于初始化阶段,则明确规定了5种情况需要对类进行初始化,分别是:

  1. 在执行下列需要引用类或接口的Java虚拟机指令时:new,getstatic,putstatic或invokestatic。这些指令通过字段或方法引用来直接或间接地引用其它类。执行上面所述的new指令,在类或接口没有被初始化过时就初始化它。执行上面的getstatic,putstatic或invokestatic指令时,那些解析好的字段或方法中的类或接口如果还没有被初始化那就初始化它。
  2. 在初次调用java.lang.invoke.MethodHandle实例时,它的执行结果为通过Java虚拟机解析出类型是2(REF_getStatic)、4(REF_putStatic)或者6(REF_invokeStatic)的方法句柄(§5.4.3.5)。
  3. 在调用JDK核心类库中的反射方法时,例如,Class类或java.lang.reflect包。
  4. 在对于类的某个子类的初始化时。
  5. 在它被选定为Java虚拟机启动时的初始类(§5.2)时。

结合上面说的,加载、验证、准备、初始化和卸载这5个阶段的顺序是固定的,需要按这个顺序开始(允许交叉),我们确定了初始化的时机,那么在初始化时或者之前,就要开始加载了。同时还有一点,也就是这个问题涉及到的场景,一个类在验证这个步骤时,会验证A类的字节码,其中可能会涉及到所以来的其他类,根据验证的具体需求,可能需要加载其他类。而这个问题具体的校验过程就是一个方法调用,涉及到类型转换赋值(传入子接口类型,需要转为父接口类型),这种情况下需要加载类型来判断是否可以进行赋值,按理是需要加载赋值左右两边的类型的,但是因为左边类型是接口,被认为都可以赋值,所以没有加载右边类型。

总结来说可能的加载时机为以下几点(不一定全面,是我目前已知的):

  1. JVM启动时会预加载一些核心类,比如Object、String等
  2. 这个类被第一次真正使用到时,比如主类因为要调用其main方法,自然需要被加载
  3. 在A类校验阶段,可能需要加载其代码中使用到的B类
  4. 在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类的时候停下来,接着分析调用堆栈:

// 加载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 <return> to continue, or q <return> 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);
}

总结

总结来说可能的加载时机为以下几点(不一定全面,是我目前已知的):

  1. JVM启动时会预加载一些核心类,比如Object、String等
  2. 这个类被第一次真正使用到时,比如主类因为要调用其main方法,自然需要被加载
  3. 在A类校验阶段,可能需要加载其代码中使用到的B类
  4. 在A类进行某个符号引用的解析时,需要加载对应的B类。比如正在执行A类的一个方法,执行到B.func(),那就需要解析B和func符号引用为直接引用,那自然需要加载B类到方法区中

对应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类。

参考资料:When is a Java Class loaded? - Stack Overflow

本文独立博客地址:JVM源码分析-类加载场景实例分析 | 木杉的博客

JVM源码分析-类加载场景实例分析的更多相关文章

  1. JVM源码分析之Metaspace解密

        概述 metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级了中间件所 ...

  2. synchronized的jvm源码分析聊锁的意义

    上篇写完了ReentrantLock源码实现,从我们的角度分析设计锁,在对比大神的实现,顺道拍了一波道哥的马屁,虽然他看不到,哈哈.这一篇我们来聊一聊synchronized的源码实现,并对比reen ...

  3. JVM源码分析之SystemGC完全解读

    JVM源码分析之SystemGC完全解读 概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可 ...

  4. JVM源码分析-JVM源码编译与调试

    要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...

  5. JVM源码分析之警惕存在内存泄漏风险的FinalReference&lpar;增强版&rpar;

    概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...

  6. JVM源码分析之堆内存的初始化

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十五篇. ​ 今天呢!灯塔君跟大家讲: JVM源码分析之堆内存的初始化   堆初始化 Java堆的初始化入口位于Univ ...

  7. JVM源码分析之JVM启动流程

      原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十四篇. 今天呢!灯塔君跟大家讲: JVM源码分析之JVM启动流程 前言: 执行Java类的main方法,程序就能运 ...

  8. JVM源码分析之Java对象头实现

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十一篇. 今天呢!灯塔君跟大家讲: JVM源码分析之Java对象头实现 HotSpot虚拟机中,对象在内存中的布局分为三 ...

  9. JVM源码分析之Object&period;wait&sol;notify实现

    ​ “365篇原创计划”第十一篇.   今天呢!灯塔君跟大家讲:   JVM源码分析之Object.wait/notify实现       最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提 ...

随机推荐

  1. jquery富文本在线编辑器UEditor

    UEditor 是由百度「FEX前端研发团队」开发的所见即所得富文本web编辑器,具有轻量,可定制,注重用户体验等特点,开源基于MIT协议,允许*使用和修改代码. UEditor的功能非常强大,官方 ...

  2. &lbrack;译&rsqb; 你该知道的javascript作用域 &lpar;javascript scope&rpar;&lpar;转&rpar;

    javascript有一些对于初学者甚至是有经验的开发者都难以理解的概念. 这个部分是针对那些听到 : 作用域, 闭包, this, 命名空间, 函数作用域, 函数作用域, 全局作用域, 变量作用域( ...

  3. quartz2D简单使用

    quartz2D绘图 1:上下文:context,这个翻译不好理解,其实翻译环境更好一点,就是给了你一个画板,你看不到而已 在: CGContextRef ctx = UIGraphicsGetCur ...

  4. CentOS7上安装Pycharm

    下载pycharm $ wget https://download.jetbrains.com/python/pycharm-professional-2016.1.2.tar.gz 解压 $ .ta ...

  5. datatables完整的增删改查

    1.需要指定datatables的ID <button class="btn btn-primary" id="newAttribute">新增证照 ...

  6. 使用 javascript 来实现 观察者模式

    以[猫叫.老鼠跑.主人醒]为例子,使用 javascript 来实现 观察者模式 (有在线演示) 2013-06-24 08:35 by 金色海洋(jyk)阳光男孩, 572 阅读, 4 评论, 收藏 ...

  7. 2017-2018-1 1623 bug终结者 冲刺002

    bug终结者 冲刺002 by 20162329 张旭升 今日冲刺任务: 能够显示主菜单和功能 游戏需要提供主菜单让玩家进行游戏设置,同时能能够把地图文件中的信息转换成为图像显示到游戏界面上 能够实现 ...

  8. 区间最深LCA

    求编号在区间[l, r]之间的两两lca的深度最大值. 例题. 解:口胡几种做法.前两种基于莫队,第三种是启发式合并 + 扫描线,第四种是lct + 线段树. ①: 有个结论就是这个答案一定是点集中D ...

  9. fetch的总结

    && ) { && ) { }); });

  10. linux centos挂载数据盘教程

    一.备份/home/liying目录数据前提条件:电脑重启下,保证服务关闭,以免进程影响操作 a.新建backup目录#cd /#mkdir backup b.把/home/liying/目录下的数据 ...