JVM-垃圾回收

目录

1、GC过程

2、垃圾回收算法

2.1、标记-清除

2.2、标记-整理

2.3、复制

2.4、分代收集算法

3、TLAB

4、对象如何进入老年代

5、卡片标记

6、HotSpot垃圾回收器

6.1、年轻代垃圾回收器

6.2、老年代垃圾回收器

6.3、如何配置垃圾回收器

6.4、STW

7、CMS垃圾回收器

7.1、定义

7.2、设计目标

7.3、CMS回收过程

7.4、内存碎片

7.5、CMS的停顿(STW)

7.6、CMS缺点

8、G1垃圾回收器

8.1、为什么需要G1?

8.2、G1的设计目标

8.3、CMS和G1的区别?

[8.4、G1 有年轻代和老年代的区分吗?](#8.4、G1 有年轻代和老年代的区分吗?)

8.5、G1的优点

8.6、G1的数据结构

8.8、G1的垃圾回收过程

8.8.1、年轻代回收(STW)

8.8.2、并发标记

[8.8.3、混合回收(Mixed GC)](#8.8.3、混合回收(Mixed GC))

9、ZGC


1、GC过程

先找到活跃的对象,然后把其他不活跃的对象判定为垃圾,然后删除。所以垃圾回收只与活跃的对象有关,和堆的大小无关。

2、垃圾回收算法

2.1、标记-清除

标记存活的对象,然后将未标记的对象统一清除。缺点是会产生内存碎片。

2.2、标记-整理

标记存活的对象,然后把整个堆中未标记的对象压缩到堆的其中一块,从而避免了内存的碎片化问题。

2.3、复制

将内存一分为二,每次只使用其中一半,满了之后将存活的对象复制到另一半内存中,然后将剩下的碎片一次性擦除。缺点是需要两倍的内存空间。

2.4、分代收集算法

  • 新生代:复制。(新生代存活对象少,复制代价小)
  • 老年代:标记清除,标记整理

年轻代 = 1*Eden + 2*survivor

当年轻代中的 Eden 区分配满的时候,就会触发年轻代的 GC(Minor GC)。具体过程如下:

1、在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区(以下简称from);

2、Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理。存活的对象会被复制到 to 区;接下来,只需要清空 from 区就可以了。

所以在这个过程中,总会有一个 Survivor 分区是空置的。Eden、from、to 的默认比例是 8:1:1,所以只会造成 10% 的空间浪费。( -XX:SurvivorRatio 进行配置的(默认为 8))

3、TLAB

Thread Local Allocation Buffer,JVM 默认给每个线程开辟一个 buffer 区域,用来加速对象分配。这个 buffer 就放在 Eden 区中。

对象的分配优先在 TLAB上 分配,但 TLAB 通常都很小,所以对象相对比较大的时候,会在 Eden 区的共享区域进行分配。

4、对象如何进入老年代

  • 提升

对象在新生代的GC存活次数阈值:‐XX:+MaxTenuringThreshold(最大为15)。即一个对象,在15次GC后,依然存活,那么将该对象提升到老年代。

  • 分配担保

当 Survivor 空间不够,就需要依赖其他内存(指老年代)进行分配担保。这个时候,对象也会直接在老年代上分配。

  • 大对象直接分配到老年代
  • 动态对象年龄判断

比如,如果幸存区中相同年龄对象大小的和,大于幸存区的一半,大于或等于 age 的对象将会直接进入老年代。

5、卡片标记

老年代是被分成众多的卡页(card page)的(一般数量是 2 的次幂)。

卡表(Card Table)就是用于标记卡页状态的一个集合,每个卡表项对应一个卡页。

如果年轻代有对象分配,而且老年代有对象指向这个新对象, 那么这个老年代对象所对应内存的卡页,就会标识为 dirty,卡表只需要非常小的存储空间就可以保留这些状态。

垃圾回收时,就可以先读这个卡表,进行快速判断。

6、HotSpot垃圾回收器

6.1、年轻代垃圾回收器

  • Serial:单线程,垃圾回收时暂停用户线程,复制算法。
  • ParNew:多线程,垃圾回收时暂停用户线程。追求降低用户停顿时间,适合交互式应用。强交互弱计算。
  • Parallel Scavenge:多线程垃圾回收器,追求 CPU 吞吐量,能够在较短时间内完成指定任务,适合没有交互的后台计算。弱交互强计算。

6.2、老年代垃圾回收器

  • Serial Old:单线程,垃圾回收时暂停用户线程,标记-整理算法。
  • Parallel Old:Parallel Scavenge 的老年代版本,追求 CPU 吞吐量。
  • CMS:垃圾回收和用户线程并发执行。以获取最短 GC 停顿时间为目标的收集器,它在垃圾收集时使得用户线程和 GC 线程能够并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。

6.3、如何配置垃圾回收器

  • -XX:+UseSerialGC 年轻代和老年代都用串行收集器
  • -XX:+UseParNewGC 年轻代使用 ParNew,老年代使用 Serial Old
  • -XX:+UseParallelGC 年轻代使用 ParallerGC,老年代使用 Serial Old
  • -XX:+UseParallelOldGC 新生代和老年代都使用并行收集器
  • -XX:+UseConcMarkSweepGC,表示年轻代使用 ParNew,老年代的用 CMS
  • -XX:+UseG1GC 使用 G1垃圾回收器
  • -XX:+UseZGC 使用 ZGC 垃圾回收器

6.4、STW

Stop the world,在垃圾回收时,暂停用户的一切线程。垃圾回收器是为了让GC时间更短,减少停顿,并不能完全消除停顿。

7、CMS垃圾回收器

7.1、定义

  • 并发标记清除垃圾收集器
  • 年轻代:复制算法
  • 老年代:并发标记-清除算法

7.2、设计目标

  • 避免在老年代 GC 时出现长时间的卡顿。
  • 它把最耗时的一些操作,做成了和应用线程并行。

7.3、CMS回收过程

初始标记(STW状态:(时间短))

1、标记直接关联GC ROOT的对象;

2、标记年轻代中对象的引用。

并发标记(并行)

1、标记所有可达的对象,耗时长,但可与用户线程并行执行。

2、将老年代中发生变化的卡页,标记为dirty状态。

并发预清理(并行)

1、重新标记老年代中状态为dirty的卡页,并清除掉dirty的状态;

2、将老年代中发生变化的卡页,标记为dirty状态。

并发可取消的预清理(可选)(并行)

在满足某些条件的时候,可以终止,比如迭代次数、有用工作量、消耗的系统时间等。

最终标记(SWT)

完成老年代中所有存活对象的标记。(处理老年代中的所有状态为dirty的卡页,且不会再增加新的dirty状态的卡页)

并发清除(并发)

删除不可达对象,并回收空间。(新产生的垃圾对象留待下次GC处理,被称作浮动垃圾)

并发重置(并发)

重置 CMS 算法相关的内部数据,为下一次 GC 循环做准备。

7.4、内存碎片

一般情况下,当老年代的使用率达到 70%,就会触发 GC。

可通过参数 -XX:CMSInitiatingOccupancyFraction 用来配置这个比例。

CMS 对老年代回收时,并没有内存的整理阶段。所以,CMS提供了两个参数来解决这个问题:

  • UseCMSCompactAtFullCollection(默认开启):表示在要进行 Full GC 的时候,进行内存碎片整理。内存整理的过程是无法并发的,所以停顿时间会变长。
  • CMSFullGCsBeforeCompaction:每隔多少次不压缩的 Full GC 后,执行一次带压缩的 Full GC。默认值为 0,表示每次进入 Full GC 时都进行碎片整理。

7.5、CMS的停顿(STW)

  1. 初始标记,这部分的停顿时间较短;
  2. Minor GC(可选),在预处理阶段对年轻代的回收,停顿由年轻代决定;
  3. 重新标记,由于 preclaen 阶段的介入,这部分停顿也较短;
  4. Serial-Old 收集老年代的停顿,主要发生在预留空间不足的情况下,时间会持续很长;
  5. Full GC,永久代空间耗尽时的操作,由于会有整理阶段,持续时间较长。

7.6、CMS缺点

  1. CPU敏感:并发标记、清除过程中因占用一部分线程可能导致应用程序变慢。
  2. 并发清除过程中会产生浮动垃圾。
  3. 标记清除算法会存在空间碎片的情况。

8、G1垃圾回收器

8.1、为什么需要G1?

CMS在发生 Minor GC 时,由于 Survivor 区已经放不下了,多出的对象只能提升(promotion)到老年代。但是此时老年代因为空间碎片的缘故,会发生 concurrent mode failure 的错误。这个时候,就需要降级为 Serail Old 垃圾回收器进行收集。

8.2、G1的设计目标

分而治之,部分收集。

在任意 1 秒的时间内,停顿不得超过 10ms。-XX:MaxGCPauseMillis=10。(控制STW时间)

8.3、CMS和G1的区别?

  • CMS回收器,是对某个年代的整体收集,无法控制收集时间;
  • G1回收器,把堆分成了很多份,把每一份当做一个小目标,控制收集时间。

8.4、G1 有年轻代和老年代的区分吗?

G1堆内存划分

  • G1也会分年轻代和老年代,只不过它们在内存上是不连续的,而是由一小份一小份(Region)组成,被称作小堆区。
  • 对象超过Region大小的50%,会被分配到黄色区域,被称作Humongous Region(连续的小堆区)。
  • 可通过-XX:G1HeapRegionSize=<N>M设置Region大小。
  • 垃圾回收的时候,会优先回收垃圾最多的小堆区。

8.5、G 1 的优点

  • 任意1秒内停顿时间不会超过10ms;
  • 停顿时间不会随着堆的增大而增大;
  • 复制算法和标记整理算法,保证无空间碎片问题。

8.6、G1的数据结构

Rset

  • 用于记录和维护Region之间的对象引用关系。
  • 把 RSet 理解成一个 Hash,key 是引用的 Region 地址,value 是引用它的对象的卡页集合。
  • 年轻代的Rset:只保存来自老年代的引用,因为年轻代的回收是针对所有年轻代Region;
  • 老年代的Rset:只保存老年代对它的引用,因为老年代回收之前,会先对年轻代进行垃圾回收。

CSet

收集集合,保存一次GC中将执行垃圾回收的区间(Region)。

8.8、G1的垃圾回收过程

8.8.1、年轻代回收(STW)

Minor GC,Eden区满的时候,会发生年轻代的垃圾回收。

跨代引用使用 RSet 数据结构来追溯,会一次性回收掉年轻代的所有 Region。

  1. 扫描根:根,可以看作是我们前面介绍的 GC Roots,加上 RSet 记录的其他 Region 的外部引用。
  2. 更新RSet:RSet 可以准确的反映老年代对所在的内存分段中对象的引用。
  3. 处理RSet:识别被老年代对象指向的 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象。
  4. 复制对象:Eden 区内存段中存活的对象会被复制到 Survivor 区中空的 Region。
  5. 处理引用:处理 Soft、Weak、Phantom、Final、JNI Weak 等引用。结束收集。

8.8.2、并发标记

当整个堆内存使用达到一定比例(默认是 45%),并发标记阶段就会被启动。

可通过参数 -XX:InitiatingHeapOccupancyPercent 进行配置。

  1. 初始标记(STW):GC ROOT扫描
  2. Root 区扫描:扫描对老年代的引用,并标记被引用的对象。
  3. 并发标记:从 GC Roots 开始对 heap 中的对象标记,标记线程与应用程序线程并行执行,并且收集各个 Region 的存活对象信息。
  4. 重新标记(STW):标记那些在并发标记阶段发生变化的对象。
  5. 清理阶段:如果发现 Region 里全是垃圾,在这个阶段会立马被清除掉。不全是垃圾的 Region,并不会被立马处理,它会在 Mixed GC 阶段,进行收集。

8.8.3、混合回收(Mixed GC)

在 Minor GC 之后,如果判断这个占比达到了某个阈值(垃圾占比),下次就会触发 Mixed GC。这个阈值,由 -XX:G1HeapWastePercent 参数进行设置(默认是堆大小的 5%)。

9、ZGC

  • 停顿时间不会超过 10ms;
  • 停顿时间不会随着堆的增大而增大(不管多大的堆都能保持在 10ms 以下);
  • 可支持几百 M,甚至几 T 的堆大小(最大支持 4T)。
  • 连逻辑上的年轻代和老年代也去掉了,只分为一块块的 page,每次进行 GC 时,都会对 page 进行压缩操作,所以没有碎片问题。

以上内容为个人学习理解,如有问题,欢迎在评论区指出。

部分内容截取自网络,如有侵权,联系作者删除。

相关推荐
guangzhi06333 小时前
JVM运行区域介绍
java·jvm
guangzhi06336 小时前
JVM垃圾回收器
jvm
guangzhi06337 小时前
JVM本地方法栈
java·jvm·面试
健康平安的活着7 小时前
JVM 调优篇7 调优案例4- 线程溢出
jvm
懵懵懂懂程序员7 小时前
JVM堆外泄露分析&解决
jvm
✞༒小郑同学༒✞14 小时前
简单了解 JVM
jvm
Flying_Fish_roe14 小时前
JVM 性能优化与调优-ZGC(Z Garbage Collector)
jvm·性能优化
一般路过糸.19 小时前
【JVM】判断对象能否回收的两种方法:引用计数算法,可达性分析算法
java·jvm·算法
程序员清风20 小时前
JVM面试真题总结(八)
jvm·面试·职场和发展
玉成22620 小时前
JVM: JDK内置命令 - JPS
java·开发语言·jvm