1.hotspot jvm Դ?源码?
2.HotSpot JVM基本原理(一)
3.JVM角度看方法调用-MethodHandle篇
4.java是如何调用native方法?hotspot源码分析必会技能
5.hotspotjvm的启动过程做了什么?
6.HotSpot启动流程
hotspot jvm Դ??
我最近建立了一个在线自习室(App:番茄ToDO)用于相互监督学习,感兴趣的源码小伙伴可以加入。自习室加入码:D5A7A
Java并发包下的源码类大多基于AQS(AbstractQueuedSynchronizer)框架实现,而AQS线程安全的源码实现依赖于两个关键类:Unsafe和LockSupport。
其中,源码Unsafe主要提供CAS操作(关于CAS,源码水果视频源码在文章《读懂AtomicInteger源码(多线程专题)》中讲解过),源码LockSupport主要提供park/unpark操作。源码实际上,源码park/unpark操作的源码最终调用还是基于Unsafe类,因此Unsafe类才是源码核心。
Unsafe类的源码实现是由native关键字说明的,这意味着这个方法是源码原生函数,是源码用C/C++语言实现的,并被编译成了DLL,源码由Java去调用。
park函数的作用是将当前调用线程阻塞,而unpark函数则是唤醒指定线程。
park是等待一个许可,unpark是为某线程提供一个许可。如果线程A调用park,除非另一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。每次调用一次park,需要有一个unpark来解锁。
并且,unpark可以先于park调用,但不管unpark先调用多少次,都只提供一个许可,不可叠加。只需要一次park来消费掉unpark带来的许可,再次调用会阻塞。
在Linux系统下,park和unpark是通过Posix线程库pthread中的mutex(互斥量)和condition(条件变量)来实现的。
简单来说,mutex和condition保护了一个叫_counter的信号量。当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。c 手机网站源码当_counter=0时线程阻塞,当_counter>0时直接设为0并返回。
每个Java线程都有一个Parker实例,Parker类的部分源码如下:
由源码可知,Parker类继承于PlatformParker,实际上是用Posix的mutex和condition来实现的。Parker类里的_counter字段,就是用来记录park和unpark是否需要阻塞的标识。
具体的执行逻辑已经用注释标记在代码中,简要来说,就是检查_counter是不是大于0,如果是,则把_counter设置为0,返回。如果等于零,继续执行,阻塞等待。
unpark直接设置_counter为1,再unlock mutex返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程。源码如下:
(如果不会下载JVM源码可以后台回复“jdk”,获得下载压缩包)
HotSpot JVM基本原理(一)
最近对JVM有了深入了解,特别是HotSpot的部分,以下是我的总结。
1. HotSpot JVM结构
HotSpot的结构图展示了JVM的流程,从类加载器将class文件加载到系统中,再分配到不同的区域,并经过编译器编译。
2. Heap结构
Java的堆分为三个代,即年轻代、老年代和永久代,称为分代管理。对象通常在年轻代创建。当对象年龄达到次垃圾回收后仍留在年轻代,则晋升至老年代。
永久代的内容通常不受垃圾回收控制,如本地接口调用的数据,这些类的网上报修 php源码卸载和本地接口的内存释放非常严格,通常放在永久代。
新生代分为三个区:Eden、S0、S1,S代表survival存活,这些将在后续讨论。
3. 内存回收和TLAB
内存回收通过TLAB(Thread Local Allocation Buffers)机制提高效率,将Eden区域划分成与线程数量相对应的区域,每个线程只能在分配给它的区域分配内存。
当线程在当前内存区域分配完毕,它将在连续区域申请新内存,这样线程就关联了多个TLAB,避免了锁机制。
4. 垃圾回收算法
垃圾回收算法主要有三种:复制算法、标记清除算法和标记整理算法。
(1)复制算法:将存活对象复制到另一个空间,提高内存利用率。
(2)标记清除算法:标记被引用对象,清除未标记对象,但可能导致内存碎片。
(3)标记整理算法:对标记清除算法的优化,不存在内存碎片,但可能产生对象拷贝的代价。
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/.invoke_MT()` 方法。通过剖析 HotSpot 源码,我们发现 `invokehandle` 指令执行时会调用 `java.lang.invoke.MethodHandleNatives::linkMethod()` 方法,该方法返回一个 `MemberName` 对象,该对象描述了一个具体的方法。通过 `MemberName` 对象,JIT 可以直接访问方法的实现,从而避免了后续的解析过程,实现了高效的调用。
我们还探讨了 `final` 关键字对 MethodHandle 性能的影响。标记为 `final` 的 MethodHandle 可以被更有效地内联,从而更接近直接调用的性能。通过分析 `MethodHandle.invoke()` 调用栈以及 HotSpot 源码,我们发现 `final` 关键字的使用与否,直接关系到后续调用栈链路能否被内联,从而影响性能。
最后,我们注意到在 `MethodHandle` 的调用链路中,某些关键步骤,如 `linkMethod`,会在第一次调用时执行,之后的调用则从常量池中获取缓存。理解这些细节有助于我们更好地优化代码性能,尤其是在涉及大量方法调用的场景中。
java是如何调用native方法?hotspot源码分析必会技能
在深入研究JDK源码,如并发包和Thread相关部分时,往往会遇到native修饰的方法,它们隐藏在层层方法的底层。native方法的存在并非偶然,它是解决Java语言与操作系统直接交互的关键。Java作为高层语言,需要JVM作为桥梁,将Java指令转换为可以直接操作系统的C或C++代码,这就是native方法的用武之地。
JDK、JRE和JVM的关系是这样的:JDK包含JRE,其中的JVM负责执行Java代码并进行操作系统间的转换。在OpenJDK源码中,特别是hotspot实现的JVM中,能找到native方法的具体实现。JNI(Java Native Interface)技术用于模拟Java调用C或C++编写的native方法,确保跨平台的兼容性。
让我们通过实践来理解这个过程。首先,创建一个简单的Java类,通过javac编译,生成JavaCallC.class文件。然后使用javah命令生成JavaCallC.h头文件,这是C语言调用Java的关键部分,需要与Java代码中的native方法签名匹配。接着,编写C代码(Cclass.c),编译成动态链接库libJavaCallC.so,并将库文件路径添加到LD_LIBRARY_PATH环境变量中。
最后,执行JavaCallC命令,如果一切顺利,会看到"Java_JavaCallC_cMethod call succ"的输出,表明Java成功调用了native方法。在尝试过程中可能会遇到各种问题,但通过一步步的调试和学习,我们可以逐步掌握这个过程。
hotspotjvm的启动过程做了什么?
HotSpot JVM启动过程涉及启动器和自身两大部分。
启动器主要负责加载Java类文件,将类文件转换为本地可执行代码,并初始化环境变量和设置。
HotSpot JVM的初始化过程则包括内存分配、类加载、方法区初始化、线程创建等步骤。
启动器通过执行Java解释器或Java虚拟机启动命令来启动HotSpot JVM,典型的启动器包括JRE/JDK自带的java[.exe]和javaw.exe。
Native应用程序也可自定义启动器实现Java启动。
《Java Performance》一书提供了高阶描述,适合深入理解HotSpot JVM启动机制。
《Java Performance》笔记第页可作为参考。
HotSpot JVM初始化大入口为Threads::create_vm函数,该函数接收JavaVMInitArgs参数,并进行VM初始化。
为了详细了解HotSpot JVM启动过程,建议阅读官方文档和相关书籍,同时也可参考JDK自带的Java launcher源代码。
HotSpot启动流程
学习HotSpot启动流程有助于深入理解程序入口和虚拟机运行机制,为后续学习提供整体把握。Launcher作为启动JVM进程的工具,根据类别可划分为正式版启动器,如在Windows下常用的java.exe和javaw.exe,其中前者保留控制台与输出信息,后者用于GUI程序,不显示输出。使用“java -help”可在控制台查看Launcher的具体使用方法和标准选项配置。
Launcher并非虚拟机核心,而是封装虚拟机执行外壳,负责加载JRE环境与动态链接库。一个JVM进程仅执行指定Java程序,多个Java程序需同时启动多个JVM进程。HotSpot中Launcher由C语言编写,与gamma共享源码,而Java源码位于不同目录。
理解Launcher执行原理对于深入HotSpot意义重大。Launcher调用HotSpot核心代码初始化JVM,维护整个生命周期。通过添加_JAVA_LAUNCHER_DEBUG=1环境变量,JVM输出详细打印,直观了解启动过程。大致步骤包括前期初始化、版本验证、创建执行环境、设置虚拟机环境、加载虚拟机、解析参数、虚拟机初始化与线程创建等。
启动入口在main.c/main方法中,调用java.c/JLI_Launch方法,该方法分为几个部分:初始化、版本验证、创建执行环境、设置虚拟机环境、加载虚拟机、参数解析与虚拟机初始化。新线程执行JavaMain()函数,完成虚拟机创建与Java程序运行。
JavaMain()方法中参数解析、虚拟机初始化、打印信息、确定主类、获取main方法并调用、获取执行结果与退出虚拟机的流程清晰。调用的关键函数如初始化虚拟机、确定主类、获取方法ID与执行方法、检查结果与销毁虚拟机,共同完成Java程序的执行。
hotspot是较新的java虚拟机技术,用来代替jit技术,那么hots
“HotSpot”指的是一个具体的JVM实现,由Longview/Animorphic发展而来,被Sun/JavaSoft收购后成为Sun的Java SE主要JVM,后被Oracle接手。而“hot spot”则是一个更泛的概念,指执行频率高的代码,实现者可以根据选择将这个概念应用到方法或执行路径级别。
HotSpot VM以其混合模式执行引擎著称,包括解释器和自适应编译器。默认情况下,所有Java方法通过解释器执行。解释器会记录每个方法的调用次数和循环次数,以此判断方法的热度。一旦方法热度足够,HotSpot VM就会启动对该方法的编译。
自适应编译允许执行引擎在执行过的代码中选择一部分进行编译,通常通过多层结构实现:一层用于初始执行,另一层用于处理编译。这与JIT编译形成对比,JIT编译在代码首次执行时立即编译,确保所有执行过的代码都被编译过。
HotSpot VM的自适应编译与严格意义上的JIT编译相比,其编译时机更晚,编译代码数量也更少。两者都属于动态编译范畴,动态编译在程序运行时进行,而静态编译在程序开始运行之前完成。
尽管HotSpot VM不使用严格的JIT编译,而是通过解释器执行初始代码,但在现代实现中,JIT编译与自适应编译的概念仍在使用。例如,JRockit VM采用纯编译执行引擎,通过两次编译实现JIT编译与自适应编译的结合,而V8 JavaScript引擎通过两层编译架构实现相同的效果。这说明JIT编译与自适应编译可以共存,且在不同JVM实现中以不同形式呈现。
JVM详解之:HotSpot VM中的Intrinsic methods
内置方法是编译器内置的方法实现,它们在给定编程语言中使用,由编译器专门处理。内置方法通常在程序请求优化时才启用,以提高效率。因为内置方法是在编译器内部实现的,所以不同的虚拟机,其内置方法是不一样的。内置方法可以在Java源代码级别看起来与非内置方法一样,但它们的区别在于JVM的实现。有些方法在普通Java代码中无法实现,如sun.misc.Unsafe.compareAndSwapInt(),只能通过JNI或内置方法来实现,实现对Java语义的扩展。在Hotspot VM中,内置方法通常在src/share/vm/classfile/vmSymbols.hpp类中。通过参数查看代码中调用的方法是否为内置方法,或者通过底层汇编语言查看。内置方法大部分都是内联方法,通过减少函数调用开销的技术实现。内置方法的实现由三种编译器完成:javac将Java源代码编译成为字节码,在这一层只有数学方法和bootstrapping的MethodHandle实现;JIT的Client Compiler (C1);JIT的Server Compiler (C2)。例如,java.lang.System.currentTimeMillis()方法在Interpreter级别没有intrinsified,因为它是一个native方法,通过JNI调用底层的C++实现。而在C1和C2级别使用intrinsified,直接调用os::javaTimeMillis(),减少JNI的使用,提升效率。内置方法的实现可以通过修改底层的JVM实现完成。Graal是一个用Java编写的JIT编译器,可以使用Java来实现Intrinsic方法,对于不熟悉C++的开发者来说非常友好。通过Graal,内置方法的实现变得简单且容易操作。内置方法是JVM中非常有用的特性,能够显著提高程序效率,是编程时值得考虑的技术之一。