JVM垃圾回收

判断一个对象是否可被回收

  • 引用计数算法:给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。无法解决循环依赖问题
  • 可达性分析算法:通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回收。

垃圾回收算法

1. 标记 - 清除

将存活的对象进行标记,然后清理掉未被标记的对象。

不足:

  • 标记和清除过程效率都不高;
  • 会产生大量不连续的内存碎片,导致无法给大对象分配内存。

2. 标记 - 整理

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

3. 标记 - 复制

将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。

主要不足是只使用了内存的一半。

现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将新生代划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor。

HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间存储放不下的对象。

4.分代收集

现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。

一般将堆分为新生代和老年代。

  • 新生代使用: 复制算法
  • 老年代使用: 标记 - 清除 或者 标记 - 整理 算法

5.总结

垃圾收集器

Serial(年轻代、单线程、标记-复制)

Serial是一种单线程的垃圾回收器,采用标记-复制算法。在进行垃圾回收时,所有的应用程序线程都会停止,只有一个线程负责执行垃圾回收任务。Serial适合于小型应用程序或者测试场景,因为其效率低下,但占用内存小。

Serial Old(老年代、单线程、标记-整理)

是一种单线程、串行的老年代垃圾收集器,采用标记-整理算法。其特点是效率低下,但是占用内存小,适合于小型应用程序或者测试场景。

Parallel(年轻代、多线程、标记-复制)

Parallel是一种多线程的垃圾回收器,采用标记-复制算法。它与Serial相比,可以利用多个CPU核心来并行处理垃圾回收任务,从而提高垃圾回收效率。Parallel适合于大规模数据的应用程序,需要更快的回收速度。

Parallel Old(老年代、多线程、标记-整理)

是一种多线程、并行的老年代垃圾收集器,采用标记-整理算法。Parallel Old在回收老年代时可以利用多线程并行处理,提高垃圾回收效率,适合于大规模数据的应用程序

Parallel Scavenge(多线程、年轻代、标记-复制)

是一种多线程、并行的新生代垃圾收集器,采用标记-复制算法。Parallel Scavenge在回收新生代时可以充分利用多核心CPU的性能优势,并且具有很短的STW停顿时间。Parallel Scavenge的目标是尽可能地达到一个可控的吞吐量。

CMS (年老兼容、多线程、标记-清除)

CMS(Concurrent Mark Sweep)是一种以最短停顿时间为目标的并发垃圾收集器,采用标记-清除算法。CMS在进行垃圾回收时可以和应用程序同时运行,避免了长时间的STW暂停。由于CMS需要花费一定的CPU资源,因此不适合对响应时间要求极高的应用程序。

G1(年老兼容、标记-复制/标记-整理)

G1(Garbage First)是一种面向服务端应用程序的垃圾收集器,将整个堆空间划分成若干个Region,针对每个Region进行垃圾回收。G1的目标是高吞吐量和低延迟,可以动态调整垃圾回收的行为以达到这个目标。G1适合于大型、长时间运行的应用系统。

JDK8默认Parallel ,在年轻代是Parallel Scavenge,老年代是Parallel Old !!!!

CMS

清除过程:

  1. 初始标记(STW) : 在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的"根对象"开始,只扫描到能够和"根对象"直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。
  2. **并发标记 **:这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。
  3. 并发预清理 :并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段"重新标记"的工作,因为下一个阶段会Stop The World。
  4. **重新标记(STW) **:这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联。
  5. 并发清理 :清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。
  6. 并发重置 :这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。

常见调节参数:

-XX:+UseConcMarkSweepGC:启用CMS垃圾回收器。
-XX:+UseCMSInitiatingOccupancyOnly:只在堆空间占用达到一定阈值时才进行CMS垃圾回收。
-XX:CMSInitiatingOccupancyFraction:指定堆空间使用达到多少百分比时触发CMS垃圾回收。
-XX:+CMSScavengeBeforeRemark:在CMS标记阶段之前执行Young GC,减少晋升到老年代的对象数目。
-XX:+CMSParallelRemarkEnabled:开启并行标记阶段,提高标记效率。
-XX:+CMSClassUnloadingEnabled:启用类卸载功能,在垃圾回收时也考虑对无用类的回收。

优势:

低延迟,尤其对于大堆来说。大部分垃圾回收过程并发执行。

劣势:

  1. 内存碎片问题。Full GC 的整理阶段,会造成较长时间的停顿。
  2. 需要预留空间,用来分配收集阶段产生的“浮动垃圾”。
  3. 使用更多的 CPU 资源,在应用运行的同时进行堆扫描。

G1

G1 的回收过程主要分为 3 过程:

  • 年轻代回收(复制算法):

    • 堆是一整块区域,但被分为更小的区域。
    • 年轻代内存是一系列不连续的区域组成的。这让调整大小变得更容易。
    • 年轻代垃圾回收,会发生 STW。年轻代垃圾回收会停止所有用户线程。
    • 年轻代垃圾回收你用多个线程并发执行的。
    • 存活对象会被复制到 survivor 区域或者老年代区域。
  • 并发标记(整理算法):堆内存使用达到一定比例(默认是 45%),并发标记阶段就会被启动

    • 初始标记(STW)
    • 根区域扫描
    • 并发标记:标记线程与应用程序线程并行执行,并且收集各个 Region 的存活对象信息。
    • 重新标记(STW):标记那些在并发标记阶段发生变化的对象(SATB算法)
  • 混合模式:不止清理年轻代,还会将老年代的一部分区域进行清理

    通过 Concurrent Marking 阶段,我们已经统计了老年代的垃圾占比。在 Minor GC 之后,如果判断这个占比达到了某个阈值,下次就会触发 Mixed GC。这个阈值,由 -XX:G1HeapWastePercent 参数进行设置(默认是堆大小的 5%)。因为这种情况下, GC 会花费很多的时间但是回收到的内存却很少。所以这个参数也是可以调整 Mixed GC 的频率的。

常见调节参数:

全局参数:
-XX:+UseG1GC:启用G1垃圾收集器。
-XX:MaxGCPauseMillis=n:设置最大停顿时间,单位是毫秒,默认值是200毫秒。
-XX:ParallelGCThreads=n:设置GC线程数,建议根据CPU核数来设定。
-XX:ConcGCThreads=n:设置并发标记阶段的线程数,建议根据CPU核数来设定。
-XX:G1HeapRegionSize=n:设置每个Region的大小,默认值是1MB。
回收器特定参数:
-XX:InitiatingHeapOccupancyPercent=n:设置触发混合回收的堆占用率,默认值是45%-XX:G1MixedGCLiveThresholdPercent=n:设置在混合回收中保留的对象占存活对象的比例,默认值是65%-XX:G1HeapWastePercent=n:设置回收后允许浪费的堆内存百分比,默认值是5%-XX:G1ReservePercent=n:设置空闲Region的预留空间百分比,默认值是10%-XX:G1MaxNewSizePercent=n:设置年轻代占整个Java Heap的最大比例,默认值是60%

优点:

  1. 可以高效地处理大堆内存:由于G1将整个堆分成多个Region,并使用优先队列来选择最需要回收的Region,所以可以高效地处理大堆内存的垃圾回收。
  2. 可以避免全局停顿:G1采用并发标记算法,可以在运行时与应用程序线程并发执行标记操作,从而减少停顿时间。
  3. 可以控制最大停顿时间:G1支持设定最大停顿时间,可以在一定程度上保证系统响应时间的稳定性。
  4. 可以防止碎片产生:由于G1会根据对象的大小和存活时间来选择优先回收的Region,所以可以有效避免内存碎片的产生。

缺点:

  1. G1收集器需要更多的CPU资源:由于G1需要在运行期进行动态调整,计算每个Region的可回收性,所以需要更多的CPU资源。
  2. G1收集器的吞吐量相对低:虽然G1能够在一定程度上降低停顿时间,但是它在执行回收操作时需要遍历整个堆,因此吞吐量相对较低。
Last Updated: