垃圾收集器

1 垃圾收集算法

1.1 标记 - 清除算法

算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点:
(1)效率问题, 标记和清除两个过程的效率都不高;
(2)空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存面不得不提前触发另一次垃圾收集动作。

1.2 标记 - 整理算法

让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点: 不会产生内存碎片
不足: 需要移动大量对象,处理效率比较低。

1.3 复制算法

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

新生代的对象每次在每次垃圾收集时都会有大批对象死去,只有少量存活,因此我们不需要按照1:1划分内存空间而是 将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块Survivor。

在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor空间。(HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1:1)

1.4 分代收集算法

当前虚拟机的垃圾收集都采用分代收集算法,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

  • 新生代使用:复制算法

    新生代中,每次收集都会有大量对象(近99%)死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。

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

    而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。“标记-清除”或“标记-整理”算法会比复制算法慢10倍以 上。

image-20230228232824370

2 垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

虽然我们对各个收集器进行比较,但并非为了挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器

对于JDK8默认的垃圾回收器是-XX:+UseParallelGC(年轻代)和-XX:+UseParallelOldGC(老年代),如果内存较大(超过4个G,只是经验值),系统对停顿时间比较敏感,我们可以使用ParNew+CMS(-XX:+UseParNewGC -XX:+UseConcMarkSweepGC**)**

image-20230228232833764

2.1 Serial收集器

  • -XX:+UseSerialGC -XX:+UseSerialOldGC
  • Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。
  • 它是单线程的收集器,只会使用一个线程进行垃圾收集工作。进行垃圾收集时必须暂停其他所有的工作线程(“Stop The World”) ,直到它收集结束。
  • 它的优点是简单高效,在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
  • Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5 以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方案

新生代采用复制算法,老年代采用标记-整理算法。

2.2 Parallel Scavenge收集器

  • -XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代)
  • Parallel收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器类似。
  • 默认的收集线程数跟cpu核数相同,当然也可以用参数(- XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。
  • Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU,获取最短的垃圾回收时间)
  • Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。

新生代采用复制算法,老年代采用标记-整理算法。

2.3 ParNew收集器

  • -XX:+UseParNewGC

  • ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。

新生代采用复制算法,老年代采用标记-整理算法。

2.4 CMS收集器

  • -XX:+UseConcMarkSweepGC(old)

  • CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体 验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程 (基本上)同时工作

  • 整个过程分为四个步骤:

    • 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
    • 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
    • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
    • 并发清除:用户可同时运行的,耗时较长。
  • 优缺点:

    • 优点:并发收集、低停顿。由于在整个过程和中最耗时的并发标记和 并发清除过程收集器程序都可以和用户线程一起工作,所以总体来说,Cms收集器的内存回收过程是与用户线程一起并发执行的。
    • 缺点:
      • 对CPU敏感,并发阶段,虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢,总吞吐量会降低
      • CMS基于标记 - 清除算法,会导致空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。,当然通过参数- XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
      • 无法处理浮动垃圾。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
  • CMS的相关核心参数

  1. -XX:+UseConcMarkSweepGC:启用cms

  2. -XX:ConcGCThreads:并发的GC线程数

  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)

  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次

  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)

  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整

  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段

  8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW

  9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

只可以用在老年代,使用“标记-清除”算法

2.5 G1收集器

垃圾收集器之G1收集器 - 楼上有只喵 (pyr9.github.io)

2.6 ZGC收集器

垃圾收集器之ZGC收集器 - 楼上有只喵 (pyr9.github.io)

3. 垃圾回收器选择?

  • JDK 1.8默认使用 ParallelJDK 1.9默之后认使用 G1
  • 100M以下用Serial,简单+高效
  • 4G以下可以用parallel,并发收集 + 高吞吐量。(标记整理,STW)
  • 4-8G可以用ParNew+CMS,并发收集 + 低停顿(注重用户体验)
  • 8G以上可以用G1,避免长时间的停顿+可预期的GC停顿周期 + 高吞吐量(筛选回收时需要STW)
  • 几百G以上用ZGC,支持TB量级的堆+ 最大GC停顿时间不超10ms

垃圾收集器
http://example.com/垃圾收集器/
作者
Panyurou
发布于
2022年9月14日
许可协议