Appearance
JVM体系结构
1. 组成
1.1 类加载子系统(Class Loader Subsystem)
组成:
- 启动类加载器(Bootstrap Class loader):加载核心类库(java.lang.*等);
- 扩展类加载器(Extension Class loader):加载 JDK 扩展库(jre/lib/ext);
- 应用类加载器(Application Class loader):加载应用类路径下的类。
特点:
双亲委派机制: 描述了类加载器在加载一个类时,所遵循的顺序和委托关系。
- 委派(Delegation): 当一个Class Loader 收到加载请求时,不会立即尝试自己加载,而是先将这个请求向上委托给自己的父类加载器去完成。
- 查找(Search): 如果父类加载器能够加载成功,则直接返回加载好的类; 如果父类加载器加载失败(在自己的路径下找不到),才会讲加载请求回退给提出请求的子类加载器,让它尝试自己加载。
当父类已经加载了某个类时,子类就不会再加载一次,保证了内存中只有一个版本的类。
提供类的命名空间隔离。
1.2 运行时数据区(RunTime Data Areas)
JVM内存区,讲内存区划分成若干个区以区分实际机器上的存储、记录和调度功能模块,细节如下:
| 区域 | 作用 | 线程共享/私有 |
|---|---|---|
| PC 寄存器(PC Register) | 保存当前正执行线程的内存地址 | 私有 |
| Java 栈(Stack) | 每个方法调用生成栈帧,存局部变量、操作数栈、动态链接 | 私有 |
| 堆(Heap) | 存放对象实例和数组,是 GC 的主要管理区域 | 共享 |
| 方法区(Method Area) | 存放类信息、常量池、静态变量、JIT 编译后的代码 | 共享 |
| 运行时常量池(Runtime Constant Pool) | 方法区的一部分,存储类的编译期常量和符号引用 | 共享 |
| 本地方法栈(Native Method Stack) | 调用本地方法时使用 | 私有 |
1.3 执行引擎(Execution Engine)
- 功能:负责执行字节码。
- 组成:
- 解释器(Interpreter):逐条执行字节码,启动快,效率低。
- 即时编译器(JIT Compiler):将热点代码编译成机器码,提高运行效率。
- 垃圾回收器(Garbage Collector):管理内存回收无用对象。
1.4 本地接口(Native Interface)
功能:提供 JVM 与操作系统、本地库的桥梁。例如JNI、JNA。
2. 内存回收策略
通常显示的内存分配策略有两种:一种是静态内存分配,另一种是动态内存分配。
2.1 静态内存分配与回收
静态内存分配是指 Java 在被编译时就能确定需要的内存空间,当程序被加载时,系统就把内存一次性分给它。 这些内存不会在程序执行时发生变化,直到程序执行结束时内存才会被回收。
2.2 动态内存分配与回收
对象的内存空间是动态分配的,所谓动态分配,就是在程序执行的时候才知道要分配的存储空间的大小,而不是 在编译时就能确定的。这个对象在什么时候被回收也是不确定的,只有等到这个对象不再使用时才会被回收。
内存的分配是在对象创建时发生的,内存的回收是以对象不再被引用为前提的。
2.3 如何检测垃圾
垃圾回收器的工作:检测垃圾,释放内存。
主要有两种实现方式:
- 引用记数法
- 可达性分析
现代 JVM ,主要采用可达性分析。
2.4 垃圾回收算法
2.4.1 复制算法(Copying)
适用区域:新生代(Eden + Survivor)
- 原理:
- 将内存分为两块:当前使用区和空闲区。
- GC 时,把存活对象复制到空闲区,清空当前区。
- 完成后两块区域交换角色。
- 优点:
- 分配效率高(指针移动)
- 无需内存碎片整理
- 缺点:
- 内存浪费,需要额外空间(通常需要半区)
- 典型应用
- Minor GC
- 新生代中的 Eden 和 Survivor
2.4.2 标记清除算法(Mark-Sweep)
- 适用区域:老年代
- 原理:
- 标记阶段:遍历 GC Roots,标记所有可达对象;
- 清除阶段:清除未标记对象。
- 优点:
- 实现简单
- 不需要额外空间
- 缺点:
- 清理后可能产生内存碎片
- 停顿时间较长(Stop-The-World)
2.4.3 标记整理算法(Mark-Compact)
- 适用区域:老年代
- 原理:
- 标记阶段:遍历 GC Roots,标记所有可达对象(和标记清除法相同);
- 整理阶段:将存活对象移到另一端,使内存连续。
- 优点:
- 无碎片
- 缺点:
- 移动对象需要时间,停顿较长。
- 典型应用
- Serial Old GC
- Parallel Old GC
2.4.4 分代回收策略(Generational Collection)
- 思想:
- 将堆分代(新生代 + 老年代)
- 短命对象频繁回收,新生代使用复制算法
- 长寿对象在老年代使用标记-清理/整理
- 优势:
- 提高 Minor GC 频率
- 减少 Full GC 频率
2.4.5 分代并行 GC 算法
2.4.5.1 Parallel GC
- 多线程执行 Minor GC 或 Major GC
- 新生代使用复制算法,多线程加速
- 老年代使用标记-整理,多线程回收
- 优势:吞吐量高,适合 CPU 充足的服务器
2.4.5.2 CMS(Concurrent Mark Sweep)
- 目标:减少停顿时间
- 流程:
- 初始标记(Stop-The-World)
- 并发标记(标记可达对象,用户线程可运行)
- 并发清理(清理无用对象)
- 重新标记(Stop-The-World,修正并发期间变化)
- 优点:停顿短
- 缺点:清理后可能产生碎片,需要老年代整理。
2.4.5.3 G1 GC(Garbage First)
- 思想:
- 将堆划分成多个 Rigion
- 并发标记,优先回收垃圾最多的区域
- 可以部分回收(Partial GC)
- 特点:
- 预测停顿时间(用户可设置目标 ms)
- 支持大堆(10GB+)
- 流程:
- 并发标记所有 Region
- 计算垃圾最多的 Region
- 优先回收这些 Region
- 部分回收 + 并发清理
2.4.5.4 ZGC / Shenandoah(低停顿 GC)
- 并发标记 + 并发压缩
- 停顿时间毫秒级别
- 支持超大堆(TB级别)
JDK 之后默认 G1。
3. GC 日制获取分析
| 工具 | 用途 |
|---|---|
| jstat | 查看 GC/内存趋势 |
| jmap -histo | 快速查看对象 Top |
| jmap/jcmd dump | 生成堆快照 |
| Arthas heap/dashboard | 线上实时分析 |
| 诊断常见来源(static、ThreadLocal、cache) | 快速定位套路 |