JVM中一次完整的垃圾回收(GC)过程。这里以HotSpot虚拟机为例,并将参考其中的一种常用的垃圾回收器:G1(Garbage-First)。
堆的划分
在HotSpot JVM中,堆(Heap)通常被划分为以下几个部分:
- 年轻代(Young Generation): 这部分进一步分为Eden区和两个Survivor区(S0和S1)。
- 老年代(Old Generation): 存放长时间存活的对象。
- 元空间(Metaspace): 存放类的元数据。
阶段1:Minor GC
1.1 初始标记(Initial Mark)
- 停止所有的应用线程(Stop-The-World, STW)。
- 标记从GC Roots直接可达的对象。
1.2 并发标记(Concurrent Mark)
- 在应用线程运行的同时,标记通过已标记对象间接可达的对象。
1.3 最终标记(Final Mark)
- 处理在并发标记阶段发生变化(如新创建的对象)的标记。
1.4 清除与复制(Evacuation)
- 删除未标记的对象,并将存活的对象复制到Survivor区或Old区。
在HotSpot JVM中,当对象在年轻代(Young Generation)的Survivor区经过一定次数的Minor GC后仍然存活,它们会被晋升(promoted)到老年代(Old Generation)。这个晋升过程也被称为“老年代担保”(Tenuring Threshold)。这里有几个关键点需要注意:
晋升阈值(Tenuring Threshold)
JVM有一个称为“晋升阈值”(Tenuring Threshold)的设置,该设置定义了一个对象在被晋升到老年代之前必须经历多少次Minor GC。这个阈值可以通过JVM参数 -XX:MaxTenuringThreshold
和 -XX:InitialTenuringThreshold
来设置。
担保机制
当Minor GC发生时,JVM需要确保所有存活的对象都有足够的空间。如果Survivor区没有足够的空间来容纳这些对象,JVM有两种选择:
- 尝试在老年代中找到空间: 如果老年代有足够的空间来容纳这些对象,则这些对象会被直接晋升到老年代,即使它们的年龄没有达到晋升阈值。
- 触发Full GC: 如果老年代没有足够的空间来容纳这些对象,JVM可能会选择触发一次Full GC来清理整个堆(包括老年代)。
动态调整
在运行时,JVM可能会动态地调整晋升阈值。这是基于之前GC活动的统计数据来进行的,目的是优化内存使用和减少Full GC的次数。
为什么需要担保机制?
担保机制的目的是为了确保堆内存的有效利用和GC性能的优化。通过将经常存活的对象移动到老年代,Minor GC可以更高效地回收年轻代,因为老年代的GC通常较少发生。
阶段2:Full GC(Major GC)
Full GC通常会清理整个堆空间,包括年轻代和老年代。
2.1 初始标记(Initial Mark)
- STW事件。
- 标记所有从GC Roots直接可达的对象。
2.2 根区域扫描(Root Region Scanning)
- 标记所有从"根区域"(如老年代)可达的对象。
2.3 并发标记(Concurrent Mark)
- 与应用线程并行,标记所有存活的对象。
2.4 最终标记(Final Mark)
- STW事件。
- 处理在并发标记期间发生变化的标记。
2.5 筛选回收(Cleanup)
- 清除不再使用的内存区域,并为即将发生的分配做准备。
额外阶段:元空间回收(Metaspace Collection)
- 如果元空间满了,会触发Full GC来清理类的元数据。
监控和调试
- JVM提供了许多标志和工具来监控和调试GC行为,如
-XX:+PrintGCDetails
和JVisualVM等。