hotspot源码
1. JVM角度看方法调用-MethodHandle篇
在我们日常编程中,方法调用主要涉及三种方式:直接调用、反射调用、以及 MethodHandle 调用。这一系列文章旨在深入探讨这三种调用方式的原理和性能分析,文章基于 JDK 1.8 版本进行阐述。在前一篇文章中,我们总结了反射调用的诸多缺点,影响性能,因此建议在热点代码中使用 MethodHandle 替代反射调用。通过与极致优化后的反射调用进行比较,我们发现 MethodHandle 在性能和直接调用方面几乎不相上下,这背后的原因是通过深入剖析 HotSpot 源码得以揭示。
接下来,我们逐一揭开 MethodHandle 的神秘面纱。首先,让我们明确,MethodHandle 是一个强类型的引用,能够直接执行,类似于反射中的 Method 类,是对目标方法的引用。它被比喻为函数指针,可以指向静态方法或实例方法、构造器或字段。MethodHandle 的执行通过 `invoke` 和 `invokeExact` 方法实现,其中 `invokeExact` 要求参数类型与底层方法的参数完全匹配,而 `invoke` 在参数类型不匹配时会做适当的调整,如包装类型。
MethodHandle 在 JDK 7 中首次引入,其相关核心类包括 MethodType 和 MethodHandles.Lookup。MethodType 用于确认方法句柄是否适配,由所指向方法的参数类型和返回类型组成;MethodHandles.Lookup 用于创建方法句柄,提供多个 API,既可以使用反射 API 中的 Method 来查找,也可以根据类、方法名以及方法句柄类型来查找。
在实际应用中,我们可以通过以下例子创建 MethodHandle 并进行方法调用。例如,尝试在外部类 MethodHandleDemo 中调用 Animal 类中的私有方法 calculation(int one, int two)。若直接使用反射调用,可能会遇到外部类无法访问 Animal 类中私有方法句柄的异常。然而,通过修改 MethodHandles.Lookup 的获取方式,改为调用 Animal 中的 getLookup() 方法,此时外部类可以正常调用私有方法,说明方法句柄的访问权限不取决于句柄的创建位置,而是取决于 Lookup 对象的创建位置。
MethodHandle 在设计时就将方法修饰符权限检查放在了通过 MethodHandles.Lookup 获取 MethodHandle 的阶段,而调用时则不会进行权限检查,避免了重复的开销。这正是 MethodHandle 相对于反射调用的一个显着优势。
接下来,我们深入探讨 MethodHandle 调用的原理。首先,我们关注 MethodHandle 的动态签名。在编译时,javac 会对 MethodHandle 的 `invoke` 方法进行动态签名处理,与普通的 `invokevirtual` 指令不同,它根据实际参数和返回类型派生符号类型描述符。此外,通过 @PolymorphicSignature 注解,可以实现多态签名处理。进一步执行时,若签名不一致,会抛出异常。
然后,我们解释了 `invoke` 方法的调用实际上不是通过 JNI(Java Native Interface)进行 native 方法调用,而是执行 `invokehandle` 指令。这一过程发生在类加载阶段,JVM 会扫描类中的所有方法,对字节码进行优化,将 `invokehandle` 指令重写为其他指令,从而实现 MethodHandle 的内联优化。这一机制同时解答了为什么 MethodHandle 虽然是一个 native 方法,却可以被 JIT(Just-In-Time)编译器进行内联优化的问题。
在解释 `invokehandle` 指令后,我们分析了如何从 `MethodHandle.invoke()` 调用到实际执行的 Java 代码中的 `java.lang.invoke.LambdaForm$MH/140435067.invoke_MT()` 方法。通过剖析 HotSpot 源码,我们发现 `invokehandle` 指令执行时会调用 `java.lang.invoke.MethodHandleNatives::linkMethod()` 方法,该方法返回一个 `MemberName` 对象,该对象描述了一个具体的方法。通过 `MemberName` 对象,JIT 可以直接访问方法的实现,从而避免了后续的解析过程,实现了高效的调用。
我们还探讨了 `final` 关键字对 MethodHandle 性能的影响。标记为 `final` 的 MethodHandle 可以被更有效地内联,从而更接近直接调用的性能。通过分析 `MethodHandle.invoke()` 调用栈以及 HotSpot 源码,我们发现 `final` 关键字的使用与否,直接关系到后续调用栈链路能否被内联,从而影响性能。
最后,我们注意到在 `MethodHandle` 的调用链路中,某些关键步骤,如 `linkMethod`,会在第一次调用时执行,之后的调用则从常量池中获取缓存。理解这些细节有助于我们更好地优化代码性能,尤其是在涉及大量方法调用的场景中。
2. 请教hotspot源码中关于Serialization Page的问题
由于读sync_state的过程不是原子的,存在一个可能的场景是thread A刚读到sync_stated,且其值是_not_synchronized,这时thread A被抢占,CPU调度给了准备发起GC的线程(不妨称为thread B),该线程将sync_stated设置为了_synchronizing,然后读其他线程的状态,看其他线程是否都已经处于block状态或者_thread_in_native状态,是的话该线程就可以开始GC了,否则它还需要等待。