JVM由什么组成
1. 类加载器(Class Loader)
类加载器的主要职责是根据类的全限定名查找并加载类的字节码文件(.class 文件),然后在内存中创建一个对应的 java.lang.Class 对象。
类加载过程主要包括三个阶段:
- 加载(Loading): 查找并导入.class 文件。
- 链接(Linking):
- 验证(Verification): 确保被加载的类符合 JVM 规范,没有安全问题。
- 准备(Preparation): 为类的静态变量分配内存,并设置默认初始值。
- 解析(Resolution): 将符号引用替换为直接引用。
- 初始化(Initialization): 执行类的构造器
<clinit>()方法,对静态变量进行初始化。
JVM 中的类加载器通常采用双亲委派模型,主要包含以下几种:
- 启动类加载器(Bootstrap Class Loader): 负责加载 Java 核心库(如
JAVA_HOME/lib目录下的类),由 C++实现。 - 扩展类加载器(Extension Class Loader): 负责加载扩展目录(如
JAVA_HOME/jre/lib/ext)中的类库。 - 应用程序类加载器(Application Class Loader): 也称为系统类加载器,负责加载用户类路径(Classpath)上的类。
- 用户自定义类加载器(User-Defined Class Loader): 允许开发者根据需求自定义类的加载方式。
2. 运行时数据区(Runtime Data Area)
运行时数据区是 JVM 在执行 Java 程序时用来存储数据的内存区域,它分为多个部分,有些是线程共享的,有些是线程私有的。
2.1 线程共享的区域:
- 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 在 Java 8 及之后版本中,方法区的实现方式变为了元空间(Metaspace),它使用的是本地内存而不是 JVM 堆内存。
- 堆(Heap): 是 JVM 所管理的内存中最大的一块,几乎所有的对象实例和数组都在这里分配内存。 堆是垃圾收集器(Garbage Collector)管理的主要区域。
2.2 线程私有的区域:
- Java 虚拟机栈(Java Virtual Machine Stack): 每个线程在创建时都会创建一个虚拟机栈。 每当一个方法被调用时,JVM 会为其创建一个栈帧(Stack Frame)并压入栈中,方法执行完毕后,栈帧出栈。 栈帧中存储了局部变量表、操作数栈、动态链接和方法出口等信息。
- 本地方法栈(Native Method Stack): 与虚拟机栈类似,但它服务于本地(Native)方法,即由 C、C++等非 Java 语言编写的方法。
- 程序计数器(Program Counter Register): 是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。 它是唯一一个在 Java 虚拟机规范中没有规定任何
OutOfMemoryError情况的区域。
3. 执行引擎(Execution Engine)
执行引擎负责执行包含在已加载类中的指令。 它读取字节码,并逐条解释或编译执行。
执行引擎主要包含以下部分:
- 解释器(Interpreter): 逐条读取字节码指令,并解释执行。
- 即时编译器(Just-In-Time, JIT Compiler): 为了提高性能,JIT 编译器会在运行时将频繁执行的“热点代码”编译成与本地平台相关的机器码,并进行缓存。 HotSpot 虚拟机默认采用混合模式,结合了解释执行和即时编译的优点。
- 垃圾收集器(Garbage Collector, GC): 垃圾收集器是 JVM 内存管理的核心功能,负责自动回收堆内存中不再被引用的对象,以释放内存空间。
4. 本地方法接口(Native Interface)
本地方法接口(Java Native Interface, JNI)是一个编程框架,它允许 Java 代码与其他语言(如 C 和 C++)编写的代码进行交互。 当 Java 程序需要调用本地方法库时,就会通过 JNI 来实现。 这为 Java 程序提供了与底层操作系统和硬件交互的能力。