JVM(Java虚拟机)是执行Java字节码的运行时环境,它将Java代码解释成机器码并执行。JVM主要分为以下几个部分:
1. 类加载子系统(Class Loader Subsystem)
- 作用:负责将Java类文件(.class)加载到内存中,并将这些类文件转化为JVM可以理解的内部数据结构。它是类加载的入口,遵循 双亲委派机制(父类委派机制),即当类加载器需要加载类时,它会首先委托给父类加载器来尝试加载,避免重复加载系统核心类(如
java.lang.String
)。 - 组成:
- 启动类加载器(Bootstrap ClassLoader):JVM的一部分,负责加载JDK的核心类库。
- 扩展类加载器(Extension ClassLoader):负责加载Java扩展包中的类。
- 应用程序类加载器(Application ClassLoader):加载用户类路径(classpath)上的类。
2. 运行时数据区(Runtime Data Areas)
JVM运行时的内存分为多个区域,不同的区域负责不同类型的数据存储。
-
堆(Heap):
- 存储所有对象和数组,堆是JVM内存中最大的一块,所有线程共享这块内存。Java对象在堆中分配内存。堆是GC(垃圾回收器)的主要工作区域。
- 堆通常又划分为新生代和老年代。新生代用于存储短生命周期的对象,老年代用于存储生命周期较长的对象。
-
方法区(Method Area):
- 也称为元空间(Metaspace,JDK 8之后的称呼),存储类结构信息(如类名、字段、方法、常量池等)。它也是一个线程共享的区域。
- 静态变量、常量池、类的字节码信息都会存储在方法区。
-
虚拟机栈(JVM Stack):
- 每个线程都会创建自己的虚拟机栈,栈中存储局部变量、操作数栈、帧数据等。栈的生命周期与线程相同,每个方法执行时都会在栈中创建一个栈帧。
- 栈帧中包含局部变量表(存放方法的参数和局部变量)、操作数栈、方法的返回地址等。
-
本地方法栈(Native Method Stack):
- 用于执行本地方法的栈。与虚拟机栈类似,每个线程有一个独立的本地方法栈,主要为JVM调用Native方法时服务(例如使用C/C++编写的代码)。
-
程序计数器(Program Counter Register):
- 每个线程都有自己的程序计数器,用来记录当前线程执行的字节码指令地址。当线程被切换时,程序计数器会记录上次执行到哪一条指令。
3. 执行引擎(Execution Engine)
JVM的核心,它负责执行字节码,将字节码转换为机器码并执行。
-
解释器(Interpreter):
- 逐条解释执行字节码,将其翻译为具体的机器码并执行。它适用于频繁调用的方法,但解释器的效率相对较低。
-
JIT编译器(Just-In-Time Compiler):
- 为了提高效率,JVM引入了JIT编译器,将一些频繁执行的代码直接编译成机器码,从而提高执行效率。JIT编译器主要优化热点代码(即多次执行的代码)。
-
垃圾回收器(Garbage Collector):
- 负责回收堆内存中的不再使用的对象。不同JVM实现会有不同的GC算法(如标记-清除算法、标记-整理算法、复制算法等)。
4. 本地方法接口(Native Interface)
- 作用:允许Java代码调用非Java代码(如C/C++编写的本地方法)。通过JNI(Java Native Interface)实现Java与其他语言的互操作,主要用于与操作系统底层交互,或者调用高效的底层代码来提高性能。
5. 垃圾回收机制(Garbage Collection, GC)
- 作用:自动管理内存,回收不再使用的对象,释放内存空间。JVM的垃圾回收机制有多种算法,包括:
- 标记-清除(Mark and Sweep):标记存活对象,清除未标记的对象。
- 复制算法(Copying):新生代常用,将对象复制到另一个区域以释放内存。
- 标记-整理(Mark-Compact):老年代常用,整理内存空间以提高连续可用空间。
- 分代收集:将堆分为新生代和老年代,分别采用不同的回收策略,适应对象的生命周期特点。
6. 即时编译器(Just-In-Time Compiler, JIT)
- JIT是一种动态编译器,当某段代码被多次执行时,JIT会将其编译为本地机器码,提高运行效率。JIT编译器与解释器配合工作,达到更好的性能。
总结
JVM是一个复杂的执行环境,通过类加载子系统加载字节码,通过运行时数据区管理内存,通过执行引擎执行字节码,并且通过垃圾回收机制自动管理内存,确保系统的稳定性和高效运行。