翻译原文:https://dzone.com/articles/jvm-architecture-explained
JVM架构解析
每一个Java开发者都知道字节码是被JRE执行。但是实际上许多人并不知道JRE是JVM的实现能解析字节码,编译代码,并且执行。作为一个开发者了解JVM的架构是非常重要的,因为它会使书写代码更加高效。在这篇文章中我们将更加深入地学习JVM架构和JVM不同的组成部分。
什么是JVM?
虚拟机是物理机器的软件实现。Java是发展于WORA (Write Once Run Anywhere)这一观点。编译器将Java文件编译成Java.class文件,然后这个java.class文件被放到JVM 中,装载并执行java.class文件。下面是一个JVM架构图表。
JVM如何工作
如图表所示,JVM分为三大部分
1. 类加载子系统
2. 运行时数据区
3. 执行引擎
1.类加载子系统
Java动态类加载功能是由类加载子系统提供的。它加载,链接,初始化在运行时第一次出现的类的class文件。
1.1 Loading
类在这一模块被加载。Boot Strap class Loader, Extension class Loader, Application class Loader 这三个loader协助实现。
1. Boot Strap class Loader,负责加载位于bootstrap classpath下的类。
2. Extension ClassLoader,负责加载位于ext folder (jre\\lib).下的类
3. Application ClassLoader,负责加载位于应用级别的路径,路径提到的环境变量等。
在加载时会有委派分层算法。
1.2 Linking
a) 验证
字节码验证者将会验证生成的字节码是否符合规范,如果验证失败将得到验证错误。
b) 准备
对于所有的静态变量,内存会分配,并设置默认值。
c) Resolve
所有的符号内存引用都被方法区的原始引用所取代。
1.3初始化
这是类加载的最后阶段,所有的静态变量将被分配原始值,静态块将被执行。
2.运行时数据区
运行时数据区主要被分为5个模块。
2.1. 方法区
所有的类级数据将被存储在这里,包括静态变量。每个JVM方法区只有一个,它是一个共享资源。
2.2. 堆区
所有的对象和他们相应的实例变量,数组都会被存储到这里,每个JVM堆区只有一个。由于方法区和堆区共享多个线程的内存,存储数据不是线程安全的。
2.3. 栈区
每一个线程,都会创建一个独立的运行时栈,对于每个方法调用,会在栈存储器中创建一个栈帧条目。所有的本地变量都会在栈内存汇总创建,说栈区域是线程安全的,因为它不是一个共享资源。栈帧被分为三个实体:
a) 局部变量数组-相关方法的局部变量及其值将会存储在这里
b) 操作数栈-如果需要任何中间操作,操作数栈将作为运行时工作区来执行操作。
c) 帧数据-对应于该方法的所有符号存储在这里。在任何异常情况下,捕获的信息将保持在帧数据中。
2.4. PC 寄存器
每个线程都有一个独立的PC 寄存器, 以保持当前执行指令的地址,一旦这条指令被执行,PC 寄存器就更新至下一条指令
2.5. 本地方法栈
本地方法栈保存了本地方法信息,对于每一个线程,将创建一个单独的本地方法栈。
3. 执行引擎
经过运行时数据区分配的字节码被执行引擎执行。执行引擎阅读字节码,并一块一块地执行。
3.1. 解释器
解释器解析字节码很快,但执行很慢。解释器的缺点是当一个方法被多次调用,每一次都需要新的解释。
3.2. JIT编译器
JIT编译器抵消了解释器的缺点。执行引擎将借助解释器的帮助转换字节码,当它发现是重复的代码时则使用JIT编译器,编译整个字节码并更改为本地代码。此本地代码将直接用于重复的方法调用,从而提高了系统的性能。
a)中间代码生成器-产生中间代码。
b)代码优化器-负责优化上面生成的中间代码。
c)目标代码生成器-负责生成机器代码或者本地代码。
d)实践探查器-一个特殊的组件,负责发现热点,如某个方法被多次调用或者未被调用。
3.3. 垃圾回收
收集和移除未引用的对象。垃圾回收可以通过调用“System.gc()”,但执行是没有保证的。JVM的垃圾回收收集已经创建的对象。
Java Native Interface (JNI):JNI将和本地方法库相互作用,并为执行引擎提供所需要的本地库。
Native Method Libraries(本地方法库):它是一个被执行引擎所需的本地库的集合。