Skip to content

JVM体系结构

1. 组成

1.1 类加载子系统(Class Loader Subsystem)

  • 组成:

    1. 启动类加载器(Bootstrap Class loader):加载核心类库(java.lang.*等);
    2. 扩展类加载器(Extension Class loader):加载 JDK 扩展库(jre/lib/ext);
    3. 应用类加载器(Application Class loader):加载应用类路径下的类。
  • 特点:

    1. 双亲委派机制: 描述了类加载器在加载一个类时,所遵循的顺序和委托关系

      • 委派(Delegation): 当一个Class Loader 收到加载请求时,不会立即尝试自己加载,而是先将这个请求向上委托给自己的父类加载器去完成。
      • 查找(Search)如果父类加载器能够加载成功,则直接返回加载好的类; 如果父类加载器加载失败(在自己的路径下找不到),才会讲加载请求回退给提出请求的子类加载器,让它尝试自己加载。

      当父类已经加载了某个类时,子类就不会再加载一次,保证了内存中只有一个版本的类。

    2. 提供类的命名空间隔离。

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)

  • 功能:负责执行字节码。
  • 组成
    1. 解释器(Interpreter):逐条执行字节码,启动快,效率低。
    2. 即时编译器(JIT Compiler):将热点代码编译成机器码,提高运行效率。
    3. 垃圾回收器(Garbage Collector):管理内存回收无用对象。

1.4 本地接口(Native Interface)

功能:提供 JVM 与操作系统、本地库的桥梁。例如JNI、JNA。

2. 内存回收策略

通常显示的内存分配策略有两种:一种是静态内存分配,另一种是动态内存分配

2.1 静态内存分配与回收

静态内存分配是指 Java 在被编译时就能确定需要的内存空间,当程序被加载时,系统就把内存一次性分给它。 这些内存不会在程序执行时发生变化,直到程序执行结束时内存才会被回收。

2.2 动态内存分配与回收

对象的内存空间是动态分配的,所谓动态分配,就是在程序执行的时候才知道要分配的存储空间的大小,而不是 在编译时就能确定的。这个对象在什么时候被回收也是不确定的,只有等到这个对象不再使用时才会被回收。

内存的分配是在对象创建时发生的,内存的回收是以对象不再被引用为前提的。

2.3 如何检测垃圾

垃圾回收器的工作:检测垃圾,释放内存。

主要有两种实现方式:

  1. 引用记数法
  2. 可达性分析

现代 JVM ,主要采用可达性分析。

2.4 垃圾回收算法

2.4.1 复制算法(Copying)

适用区域:新生代(Eden + Survivor)

  • 原理
    • 将内存分为两块:当前使用区和空闲区。
    • GC 时,把存活对象复制到空闲区,清空当前区。
    • 完成后两块区域交换角色。
  • 优点:
    • 分配效率高(指针移动)
    • 无需内存碎片整理
  • 缺点
    • 内存浪费,需要额外空间(通常需要半区)
  • 典型应用
    • Minor GC
    • 新生代中的 Eden 和 Survivor

2.4.2 标记清除算法(Mark-Sweep)

  • 适用区域:老年代
  • 原理
    1. 标记阶段:遍历 GC Roots,标记所有可达对象;
    2. 清除阶段:清除未标记对象。
  • 优点
    • 实现简单
    • 不需要额外空间
  • 缺点
    • 清理后可能产生内存碎片
    • 停顿时间较长(Stop-The-World)

2.4.3 标记整理算法(Mark-Compact)

  • 适用区域:老年代
  • 原理
    1. 标记阶段:遍历 GC Roots,标记所有可达对象(和标记清除法相同);
    2. 整理阶段:将存活对象移到另一端,使内存连续。
  • 优点
    • 无碎片
  • 缺点
    • 移动对象需要时间,停顿较长。
  • 典型应用
    • 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)
  • 目标:减少停顿时间
  • 流程
    1. 初始标记(Stop-The-World)
    2. 并发标记(标记可达对象,用户线程可运行)
    3. 并发清理(清理无用对象)
    4. 重新标记(Stop-The-World,修正并发期间变化)
  • 优点:停顿短
  • 缺点:清理后可能产生碎片,需要老年代整理。
2.4.5.3 G1 GC(Garbage First)
  • 思想
    • 将堆划分成多个 Rigion
    • 并发标记,优先回收垃圾最多的区域
    • 可以部分回收(Partial GC)
  • 特点
    • 预测停顿时间(用户可设置目标 ms)
    • 支持大堆(10GB+)
  • 流程
    1. 并发标记所有 Region
    2. 计算垃圾最多的 Region
    3. 优先回收这些 Region
    4. 部分回收 + 并发清理
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)快速定位套路