目录
[8.4、G1 有年轻代和老年代的区分吗?](#8.4、G1 有年轻代和老年代的区分吗?)
[8.8.3、混合回收(Mixed GC)](#8.8.3、混合回收(Mixed GC))
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)
- 初始标记,这部分的停顿时间较短;
- Minor GC(可选),在预处理阶段对年轻代的回收,停顿由年轻代决定;
- 重新标记,由于 preclaen 阶段的介入,这部分停顿也较短;
- Serial-Old 收集老年代的停顿,主要发生在预留空间不足的情况下,时间会持续很长;
- Full GC,永久代空间耗尽时的操作,由于会有整理阶段,持续时间较长。
7.6、CMS缺点
- CPU敏感:并发标记、清除过程中因占用一部分线程可能导致应用程序变慢。
- 并发清除过程中会产生浮动垃圾。
- 标记清除算法会存在空间碎片的情况。
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。
- 扫描根:根,可以看作是我们前面介绍的 GC Roots,加上 RSet 记录的其他 Region 的外部引用。
- 更新RSet:RSet 可以准确的反映老年代对所在的内存分段中对象的引用。
- 处理RSet:识别被老年代对象指向的 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象。
- 复制对象:Eden 区内存段中存活的对象会被复制到 Survivor 区中空的 Region。
- 处理引用:处理 Soft、Weak、Phantom、Final、JNI Weak 等引用。结束收集。
8.8.2、并发标记
当整个堆内存使用达到一定比例(默认是 45%),并发标记阶段就会被启动。
可通过参数 -XX:InitiatingHeapOccupancyPercent 进行配置。
- 初始标记(STW):GC ROOT扫描
- Root 区扫描:扫描对老年代的引用,并标记被引用的对象。
- 并发标记:从 GC Roots 开始对 heap 中的对象标记,标记线程与应用程序线程并行执行,并且收集各个 Region 的存活对象信息。
- 重新标记(STW):标记那些在并发标记阶段发生变化的对象。
- 清理阶段:如果发现 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 进行压缩操作,所以没有碎片问题。
以上内容为个人学习理解,如有问题,欢迎在评论区指出。
部分内容截取自网络,如有侵权,联系作者删除。