JVM 体系结构
本章将深入介绍JVM的整体架构,包括各个核心组件及其作用。理解JVM体系结构是深入学习JVM的基础。
JVM 整体架构概览
JVM在执行Java程序时会经历以下主要阶段:
执行流程说明:
- 编写源程序:编写.java文件
- 编译:使用javac编译器将.java文件编译成.class字节码文件
- 类加载:类加载子系统将.class文件加载到JVM
- 运行时数据区:JVM为程序运行划分不同的内存区域
- 执行引擎:解释器/JIT编译器执行字节码,垃圾回收器管理内存
- 本地交互:通过JNI与本地方法库交互
类加载子系统
类加载子系统(Class Loader Subsystem)负责将class文件加载到JVM中。
类加载器的层次结构
-
Bootstrap Class Loader(启动类加载器)
- 由C++实现,是JVM的一部分
- 负责加载Java核心类库(
java.lang.*、java.util.*等) - 不继承自
java.lang.ClassLoader - 加载路径由
java.home属性指定
-
Extension Class Loader(扩展类加载器)
- Java语言实现,继承自
URLClassLoader - 负责加载Java扩展库(
javax.*及JAVA_HOME/lib/ext目录下的类) - 父加载器是Bootstrap Class Loader
- Java语言实现,继承自
-
Application Class Loader(应用类加载器)
- Java语言实现,继承自
URLClassLoader - 负责加载应用程序classpath下的类
- 父加载器是Extension Class Loader
- Java语言实现,继承自
-
用户自定义类加载器
- 继承
java.lang.ClassLoader - 用于特殊场景,如热部署、代码加密等
- 继承
双亲委派机制
JVM采用双亲委派模型(Parent Delegation Model)来保证类的安全加载:
工作原理:
- 当类加载器收到加载请求时,首先委派给父加载器
- 父加载器再委派给自己的父加载器
- 直到Bootstrap Class Loader
- 如果父加载器无法完成加载,才由自己尝试加载
优势:
- 避免类的重复加载
- 保证类的安全性和一致性
- 例如:
java.lang.String始终由Bootstrap加载,不会被自定义类替代
运行时数据区
运行时数据区(Runtime Data Areas)是JVM管理的内存区域,用于存储程序运行时的数据。
内存区域划分
程序计数器(Program Counter Register)
定义:一块较小的内存空间,记录当前线程执行的字节码行号。
特点:
- 线程私有:每个线程都有独立的程序计数器
- 生命周期:随线程创建而创建,随线程销毁而销毁
- 不会出现OutOfMemoryError
作用:
- 字节码解释器通过改变计数器的值来选取下一条需要执行的字节码指令
- 分支、循环、异常处理、线程恢复等都依赖计数器
虚拟机栈(VM Stack)
定义:描述Java方法执行的线程内存模型,每个方法执行时都会创建一个栈帧(Stack Frame)。
栈帧包含:
- 局部变量表:存放方法参数和局部变量
- 操作数栈:用于计算的操作数栈
- 动态链接:指向运行时常量池的方法引用
- 方法返回地址:方法正常或异常退出的返回地址
异常情况:
- StackOverflowError:栈深度超过限制
- OutOfMemoryError:无法申请足够内存
本地方法栈(Native Method Stack)
定义:为JVM使用到的Native方法提供内存空间。
与虚拟机栈的区别:
- 虚拟机栈为Java方法服务
- 本地方法栈为Native方法服务
注意:HotSpot VM将虚拟机栈和本地方法栈合并实现。
堆(Heap)
定义:JVM管理的最大一块内存,用于存放对象实例和数组。
特点:
- 线程共享:所有线程都可以访问堆内存
- 在JVM启动时创建
- 是垃圾回收的主要管理区域
内存分配:
- 新生代(Young Generation):存放新创建的对象
- Eden区:新对象分配内存的区域
- Survivor区: Minor GC后存活对象的区域
- 老年代(Old Generation):存放长期存活的对象
方法区(Method Area)
定义:存储类信息(类的元数据)、常量、静态变量、JIT编译后的代码等。
特点:
- 线程共享
- 在JVM启动时创建
- 是JVM规范中的逻辑分区
注意:
- 在HotSpot VM中,方法区被称为"永久代"(JDK7及之前)
- JDK8后,使用元空间(Metaspace)替代,位于本地内存中
存储内容:
- 类的元数据信息
- 常量池
- 静态变量
- 即时编译器编译后的代码
- 符号引用
运行时常量池
定义:是方法区的一部分,存放编译期生成的各种字面量和符号引用。
内容:
- 字面量:文本字符串、final常量值等
- 符号引用:类和接口的全限定名、字段名称和描述符、方法名称和描述符等
执行引擎
执行引擎(Execution Engine)负责执行字节码指令。
解释器
工作方式:逐行解释字节码为机器码执行。
特点:
- 启动快
- 执行慢
- 适合小规模程序
JIT 编译器
工作方式:将热点代码(Hot Spot Code)编译为机器码,缓存起来供后续使用。
热点代码检测:
- 方法调用计数器:统计方法被调用的次数
- 回边计数器:统计循环体执行的次数
编译器类型:
- Client Compiler(C1编译器):编译速度快,优化程度低
- Server Compiler(C2编译器):编译速度慢,优化程度高
分层编译(Tiered Compilation):
- 解释执行
- C1编译
- C2编译
垃圾回收器
自动回收无用对象占用的内存,是JVM最重要的组件之一。详细内容将在后续章节介绍。
本地库接口
JNI(Java Native Interface):Java调用本地方法的桥梁。
作用:
- 调用C/C++编写的本地库
- 与操作系统交互
- 提高特定场景的性能
总结
JVM是一个复杂的系统,由多个组件协同工作:
- 类加载子系统:负责将class文件加载到内存
- 运行时数据区:管理程序运行时的各种数据
- 执行引擎:执行字节码指令
- 本地库接口:与本地方法交互
理解JVM的体系结构,有助于:
- 编写更高效的Java代码
- 排查和解决性能问题
- 深入理解Java语言的运行机制