G1原理—4.G1垃圾回收的过程之Young GC
大纲
1.G1的YGC过程
2.YGC并行处理阶段的过程
3.YGC串行处理阶段的过程(一)
4.YGC串行处理阶段的过程(二)
5.整个YGC的执行流程总结
1.G1的YGC过程
(1)YGC相关的一些参数
(2)YGC和MixedGC、FGC之间的关系
(3)YGC使用的算法 + 新生代的垃圾回收流程
(1)YGC相关的一些参数
一.-XX:+UseG1GC
设置使用G1垃圾回收器。
二.-XX:G1HeapRegionSize
设置Region分区大小,最小值1M,最大值32M,且只能是2的n次幂。
三.-Xms和-Xmx或者InitialHeapSize和MaxHeapSize
设置堆内存最大值最小值。
四.-XX:NewSize和-XX:MaxNewSize
设置新生代最小值和最大值。注意,在G1里这个最大值最小值其实是可以不设置的。G1会自动计算出一个值:从5%的Region数量开始,慢慢增加到最大为60%的Region数量。一般不用指定新生代的最大值和最小值,按照默认的5%~60%即可。
五.新生代Region数量
下限-XX:G1NewSizeRercent,默认5%
上限-XX:G1MaxNewSizePercent,默认60%
六.新生代Eden和Survivor的比例:-XX:SurvivorRatio=n
默认为8,即eden : s1 : s2 = 8 : 1 : 1。这个比例和ParNew的原理是一致的,比如总的新生代是100个Region,那么Eden区有80个,两个S区各有10个。
七.-XX:MaxGCPauseMills=n
设置最大GC暂停时间,这是一个大概值,JVM会尽可能的满足此值。例如设置200ms,那么G1就会在每次GC时努力保证GC的停顿时间在这个范围内。
八.-XX:NewRatio=n
新生代与老年代的大小比例,默认值2。只设置一个NewRatio,与只设置一个Xmn是相当的。最好不要设置一个Xmn,或者最好不要单独设置一个NewRatio。因为这样会固定新生代大小,不利于停顿时间预测。
九.-XX:ParallelGCThreads=n
参与回收的线程数量,默认和CPU核数相等。
(2)YGC和Mixed GC、FGC之间的关系
G1中的YGC、Mixed GC以及FGC和ParNew + CMS是有相似之处的。比如,在新生代GC后,会有存活对象进入老年代。如果老年代对象占用达到了某个阈值,就会触发老年代的回收。在ParNew + CMS中,是直接触发FGC,而在G1中是触发Mixed GC。
在G1中首先会进行YGC,YGC会选择所有新生代的分区进行回收。当程序不断运行,存活对象越来越多,老年代的对象越来越多时,就会在某次YGC的时候,触发一个并发标记过程。
然后等待YGC和这次并发标记过程结束后,就会正式进入Mixed GC。Mixed GC会从老年代中选择部分回收价值比较高的Region进行回收,从而满足用户设置的MaxGCPauseMills值,当然Mixed GC也会回收所有新生代分区。当Mixed GC后,对象还是无法分配成功时,就会触发FGC。FGC会暂停程序运行,对整个堆进行全面的垃圾回收。FGC的回收会包括新生代、老年代、大对象等。
YGC、Mixed GC、FGC间的过程转换关系如下:
(3)YGC使用的算法 + 新生代的垃圾回收流程
一.不均匀的分区分布
二.对象在Eden区的分布
三.Eden区占满时触发YGC
四.标记存活对象
五.复制存活对象到Survivor区
六.回收垃圾对象
七.动态调整新生代区域Region数量
八.是否需要开启并发标记
九.新生代的垃圾回收流程结束
YGC使用的算法是复制算法,也就是会把新生代的所有Region按照Eden、Survivor做类型标识。在执行垃圾回收时,首先对存活对象进行标记。然后把存活对象复制到S区,接着就把所有的垃圾对象全部回收掉。注意:G1的Region分布,对于一个分代而言,不一定是连续的。
一.不均匀的分区分布
二.对象在Eden区的分布
三.Eden区占满时触发YGC
四.标记存活对象
首先从GC Roots出发,标记直接引用的对象,然后再一个一个标记GC Roots间接引用的对象。
五.复制存活对象到Survivor区
六.回收垃圾对象
如果是ParNew + CMS的新生代回收,其实到这里就基本上是结束了。
七.动态调整新生代区域Region数量
YGC回收掉的那些Region,是有可能会成为自由分区的。因为YGC回收后会动态判断,如果需要更多的Region就进行增加。如果回收时间太长,发现YGC下一次可能没有那么强的能力,那么G1就会减少几个Region。
八.是否需要开启并发标记
如果老年代的使用率达到了阈值,就开启并发标记。
九.新生代的垃圾回收流程结束
以上就是G1新生代垃圾回收的基本流程。
(4)总结
G1的YGC过程:
一.YGC相关的一些参数
二.YGC和MixedGC、FGC是什么关系
三.YGC使用的算法 + 新生代的垃圾回收流程
1.不均匀的分区分布
2.对象在Eden区的分布
3.Eden区占满时触发YGC
4.标记存活对象
5.复制存活对象到Survivor区
6.回收垃圾对象
7.动态调整新生代区域Region数量
8.是否需要开启并发标记
9.新生代的垃圾回收流程结束
2.YGC并行处理阶段的过程
(1)YGC的并行处理是什么
(2)GC Roots并行标记及RSet并行更新
(3)YGC并行标记阶段不仅会标记还会复制
(4)完成初始标记后的处理——将直接引用的对象的字段入栈
(5)遍历栈中的字段找出存活对象并复制到S区然后清理对象 + 清空栈
YGC有并行处理的过程以及串行处理的过程。
(1)YGC的并行处理是什么
YGC的过程,肯定是要做一些并行化处理的,否则速度就会比较慢。比如,标记对象时就不能一个一个对象去查找标记。所以,会有线程对GC Roots直接引用的对象进行标记。
(2)GC Roots并行标记及RSet并行更新
不仅在对GC Roots引用的对象进行标记时,会使用并行处理的方式。在对RSet进行更新时,也会用并行处理的方式。
G1在进行YGC时:会从RSet和GC Roots出发遍历所有新生代对象,然后标记存活对象。由于RSet的更新不一定会在YGC前就更新完毕,所以在YGC并行处理这个阶段,还要对RSet做并行处理的更新。即把DCQS里还没处理完毕的跨代引用关系变更,更新到RSet里面。
更新RSet完成后,再从RSet出发,去标记被RSet指向的老年代空间里的对象直接引用的新生代对象。
整个标记GC Roots + 更新RSet的过程,是由多个线程一起并行处理的。比如现在有4个GC线程参与垃圾回收,那么就会有两个线程从GC Roots出发去标记对象,有两个线程去消费DCQS然后更新RSet。接着Rset更新完毕后,就把RSet作为GC Roots继续去执行对象标记工作。
(3)YGC并行标记阶段不仅会标记还会复制
在YGC的并行标记阶段,不仅仅会根据GC Roots + RSet来追踪所有直接引用的对象。由于在执行YGC的过程中,复制操作和标记操作是同时进行的。所以在用GC Roots标记直接引用的存活对象时,也会进行复制操作。比如发现4个对象是由GC Roots直接引用的。
此时通过GC Roots找到这4个对象后,就会复制它们到一个Survivor区。
所以YGC里的复制算法,并不是等待全部标记完成,再去复制对象。而是找到一个直接引用的存活对象,就会复制到Survivor里了。
另外,把RSet作为GC Roots的意思是:RSet中映射到卡表对应的卡页中的所有对象都会作为GC Roots。因为卡页本身很小,对象数量也很少,所以可以把RSet都作为GC Roots。然后找到这些GC Roots直接引用的对象,再复制到Survivor区。
(4)完成初始标记后的处理——将直接引用的对象的字段入栈
仅仅处理这些GC Roots直接引用的对象还是不够的,因为还有很多对象会被它们间接引用。间接引用的对象,也需要全部找到并进行标记。
那么在并行处理阶段,GC线程还需要做的另外一件事就是:把刚刚找到的被GC Roots直接引用的哪些对象的字段Field,全部都给放入一个栈里面。
为什么要这么做?因为要把这些对象引用的所有对象都找到才行,找到它们引用的对象才能找到所有存活的对象。所以在把GC Roots + RSet直接引用的对象复制到S区时,就会把它们的所有字段放入一个栈中。
(5)遍历栈中的字段找出存活对象并复制到S区然后清理对象 + 清空栈
等到所有的GC Roots + RSet直接引用的对象都复制完毕后,再逐一对栈中的字段Feild进行遍历,找到所有存活的对象,然后再把找到的存活对象放入Survivor区中。
最后一口气回收掉所有的垃圾对象。
此时栈就会被清空掉了。
至此,YGC的并行操作基本已经结束,基本上YGC其实已经结束了。但是,实际上还会有后续的很多操作。比如以下操作就是在YGC的串行执行过程中需要做的,需要更新RSet、RSet卡表、释放被回收垃圾占用的Region、动态调整新生代分区数量来实现停顿预测模型等。
(6)总结
YGC并行处理阶段的过程:
一.YGC的并行处理是什么
二.GC Roots并行标记及RSet并行更新
三.YGC并行标记阶段不仅会标记还会复制
四.完成初始标记后的处理——将直接引用的对象的字段入栈
YGC的并行处理阶段具体会做的事情:
一.并行更新RSet
二.将更新完RSet加入GC Roots进行并行标记
三.并行复制直接引用的存活对象进入S区
四.将直接引用的存活对象的所有字段入栈
五.遍历栈的所有字段寻找所有存活对象
六.复制所有存活对象进入S区
七.清空全部垃圾对象
八.清空栈里的所有字段
3.YGC串行处理阶段的过程(一)
(1)YGC中的串行处理是什么
(2)YGC中的串行处理操作有哪些
一.软引用、弱引用、虚引用的处理
二.整理卡表
三.Redirty操作——清理旧RSet建立新RSet
四.释放分区
(1)YGC中的串行处理是什么
所谓串行处理,就是要一步步操作,否则就可能会出现错乱的一些操作。JVM会对垃圾回收中的一些操作使用串行化的处理方式。可能因为这些操作会有前后影响、或消耗的时间很少,所以才用串行化。当然不排除JVM后面可能会把这些操作优化成并行化的处理方式。
其中G1中的GC Roots追踪、RSet更新,这两个操作是可以并行进行的,因为这两个操作基本上不会出现互相影响的情况。但是YGC的其他一些操作,是有可能会出现先后影响的。
(2)YGC中的串行处理操作有哪些
一.软引用、弱引用、虚引用的处理
该操作是把这些引用中使用的存活对象也复制到新分区,否则就会出错。YGC中的并发处理阶段针对的是强引用对象。
软引用的回收时机:在第一次FGC时,是不回收软引用的。只有在第二次Full GC时,才会回收软引用。
在YGC执行串行处理操作时:就会把新生代里被这类引用给引用到的对象复制到Survivor区中。
二.整理卡表
卡表是一个全局卡表。在新生代在回收后,有些对象已被回收清除了,有些对象已经换了位置。这时就要把卡表中这些对象的描述数据也给清除掉和更新掉。
因为卡表中是一个字节描述512字节的内存空间。如果某内存空间被清除了,那么卡表的描述数据也需清空,否则会出错。
所以整理卡表的操作就是把已清理过的Region对应的卡表进行清空,同时把对象复制后所在的Region对应的卡表也进行修改,从而保证卡表中的描述数据是正确的。整理卡表的这个过程是很快的。
三.Redirty操作——清理旧RSet建立新RSet
这一步的主要操作,其实就是重构一下Rset。在做完垃圾回收后,新生代对象因为复制,其地址已经发生变化了。那么老年代引用的新生代对象所在Region的RSet此时还没有修改,因此需要把这个旧的RSet进行清理,然后建立一个新生代对象所在的新Region的RSet。重构RSet的过程也是很快的。
四.释放分区
新生代GC,需要把所有的非Survivor区的新生代Region都给清理掉。此时它们还被标记为Eden,或者Survivor(原本就可能有一些垃圾对象)。清理后,需要把这些Region分区给释放掉,否则需要分区时可能不够用。释放分区就是清空这些分区的标记,然后把清空后的分区加入到自由分区列表。
(3)总结
YGC串行处理阶段的过程:
一.YGC中的串行处理是什么
二.YGC中的串行处理操作有哪些
1.软引用、弱引用、虚引用的处理
2.整理卡表
3.Redirty操作——清理旧RSet建立新RSet
4.释放分区
(4)问题
如果我们是G1的开发者,在上面的流程结束之后还需要做什么?G1本身的设计思路就是,要垃圾回收优先,要满足系统的停顿时间。那么在GC之后最重要的事情是什么呢?
一.停顿预测模型和Region数量分配有关;
二.RSet处理时是比较耗时的,GC开启时Refine线程就会暂停,由GC线程来继续执行后续的操作。那么对于这个RSet、DCQ、DCQS的处理,是否需要调整?
三.是否需要扩展内存?
4.YGC串行处理阶段的过程(二)
(1)尝试对大对象进行回收(性价比很高)
(2)尝试扩展内存
(3)调整新生代分区的数目及Refine线程阈值
(4)尝试启动并发标记
(1)尝试对大对象进行回收(性价比很高)
一.为什么要对大对象尝试进行回收操作
原因一:大对象本身占用很多空间的,最少也会占1/2的Region
假如大对象能够回收,就顺带把它回收掉,这样就能腾出一块非常可观的空间出来了。
原因二:大对象回收起来不麻烦
因为大对象创建时是单独存储在一个分区(多个分区)的,属于单独存储。
二.如何判断大对象是否存活
由于每个Region都维护了一个RSet,并且RSet里存储的是引用关系信息。那么在YGC的串行处理阶段查看大对象所在Region的RSet,就能知道是否有其他对象在引用了。
注意大对象所在的Region的RSet不会有很多内容,最多就是两个对象被引用的关系。
所以如果大对象没有横跨多个分区,则只需判断一下大对象所在的Region的RSet里是否有内容,就可以判断大对象是否存活了。
如果大对象横跨多个分区,那么直接判断大对象所在的第一个Region的RSet里是否有内容,就可以知道大对象是否被引用了。
通过简单的判断就可能回收大量的空间,性价比非常高。所以要在YGC的串行处理阶段尝试一下对大对象进行回收。
(2)尝试扩展内存
前面介绍新生代内存时,介绍过可能会对新生代内存进行扩展,在YGC的串行处理阶段尝试扩展内存就是扩展新生代内存的时机之一。
完成YGC后会统计一下执行这次YGC的花费时间,而且还会统计一下在执行YGC前的系统运行总时间。于是就可以判断,这次YGC执行时间和系统运行总时间的比例是否合理。如果不合理,就要考虑扩展一下新生代内存,如果合理就没必要扩展了。
对应的参数是:GCTimeRatio和G1ExpandByPercentOfAvailable。其中GCTimeRatio是指:程序运行时间与YGC时间的比例。如果YGC时间占程序运行时间比例超过10%,就说明要扩展新生代内存。
为什么YGC时间占程序运行时间的比例超过10%就要扩展新生代内存?因为YGC时间占比超过10%,就说明要么YGC频繁、要么YGC时间太长。如果新生代空间足够大,加上G1会自己动态调整新生代分区的数量,那么就是YGC太频繁导致YGC的时间占程序运行时间比例超过10%。
YGC过于频繁,必然会导致判断出大量对象存活,相当于变相拖慢YGC。YGC中真正耗时的不是清理大量垃圾对象的过程,而是进行标记的过程。YGC中存活对象越多,进行标记的过程就越长。YGC越频繁 -> 说明新生代很快满了 -> 说明新生代需要扩展内存
如果YGC时间占程序运行时间的比例没有超过10%,则暂时不需要扩展,扩展的内存大小和G1ExpandByPercentOfAvailable有关。
(3)调整新生代分区的数目及Refine线程阈值
一.调整新生代分区数目
这个是YGC串行处理阶段的一个重点,因为对于G1来说,控制停顿时间是非常重要的。
要想控制好停顿时间:只能在系统运行时间和YGC过程中各个步骤的耗时上进行综合考量。综合考量后还要进行动态调整,这样才能保证停顿时间是可以被满足的。
那么在YGC后,首先就需要判断一下:现在的YGC耗时、YGC能力能否让下一次GC满足预期停顿时间。如果不能的话,那么就需要把新生代分区减少一些,不然就满足不了了。如果远远没达到停顿预测时间的阈值,那么就可以增加一些新生代分区。
所以这一步,就会根据当前YGC的执行时间和目标停顿时间,进行预测。看下一次YGC最多能回收多少分区,然后和当前新生代的总分区数对比。如果下次最多能回收的分区和当前新生代总分区数差不多,则无需调整。如果发现预测出来下一次YGC能回收1000个分区,而现在才600个分区。那么就可以多增加几个分区到新生代里,避免浪费堆内存。
二.调整RefinementZone的阈值
关于DCQS、DCQ和Refine线程的处理:如果DCQ比较多,则需要启动多个Refine线程去进行处理。而且在YGC开始时,这些Refine线程就会暂停,并且由YGC线程接管其工作来处理后续的DCQ。
如果YGC线程处理DCQ的时间过多,那么代表了什么?代表Refine线程的数量,或者DCQS的四个区域设置得不合理。如果设置合理,Refine线程在对应的区域中,就可满足DCQ消息的处理。此时YGC线程最多就是进行少部分的收尾工作,但现在YGC线程还需要大量的时间去处理DCQ消息,那么就说明:要么这几个DCQS的阈值设置得过大了、要么Refine线程太少了。
Refine线程理论上是不能在GC过程中动态调整上限的,所以我们只能调整DCQS的白绿黄红几个阈值的大小,通过白绿黄红来匹配Refine线程的处理能力。
比如把DCQS的各个阈值给降下来,然后把总长度也降下来。让系统线程也帮忙处理DCQ,这样就可以让GC线程的压力小一点。所以如果YGC处理DCQ时间过长,会导致DCQS的长度和阈值动态减小。
(4)尝试启动并发标记
这个过程也是一个尝试的过程。因为新生代的回收是一直在进行的,老年代的对象也是一直在累积的。
如果老年代对象累积到一定程度,那么此时就需要回收一部分老年代的垃圾对象,否则内存使用率就会太高。所以,在老年代达到45%的内存使用率时,一次YGC结束后就会开启一个并发标记过程。
如下图示:老年代占用内存达到阈值的判断,就是判断是否要进入MGC + YGC过程。如果成功启动了并发标记,就意味着接下来要进入Mixed GC了。
5.整个YGC的执行流程总结
YGC算法的流转过程:
G1原理—4.G1垃圾回收的过程之Young GC的更多相关文章
- 垃圾回收基本算法 内存管理 GC大统一理论
<垃圾收集> (豆瓣) https://book.douban.com/subject/1157908/ 第1章 简介1.1 内存分配的历史1.1.1 静态分配1.1.2 栈分配1.1.3 ...
- JVM垃圾回收(二)- Minor GC vs Major GC vs Full GC
Minor GC vs Major GC vs Full GC 垃圾回收的活动会清理对内存中的不同区域,这些事件一般被称为Minor,Major以及Full GC events.本章我们会讨论这些清理 ...
- 常见GC算法,CMS以及G1的垃圾回收过程,CMS的各个阶段哪两个是Stop the world的,CMS会不会产生碎片,G1的优势。
常见GC算法 在C/C++中是由程序员自己去申请.管理和释放内存的,因此没有GC的概念.而在Java中,专门有一个用于垃圾回收的后台线程来进行监控.扫描,自动将一些无用的内存进行释放.下面介绍几种常见 ...
- 精华推荐 | 【JVM深层系列】「GC底层调优系列」一文带你彻底加强夯实底层原理之GC垃圾回收技术的分析指南(GC原理透析)
前提介绍 很多小伙伴,都跟我反馈,说自己总是对JVM这一块的学习和认识不够扎实也不够成熟,因为JVM的一些特性以及运作机制总是混淆以及不确定,导致面试和工作实战中出现了很多的纰漏和短板,解决广大小伙伴 ...
- Java GC机制简要总结(Java垃圾回收的基本工作原理)
第一次编辑 2019-05-07 01:09:39 垃圾回收的对象 程序中的不可用对象(不存活的对象,没有任何引用),或者无用的变量信息等,在程序中长期存在会逐渐占用较多的内存空间,导致没有足够的空间 ...
- go GC垃圾回收原理
目录 1.前言 2. 垃圾回收算法 3. Golang垃圾回收 3.1 垃圾回收原理 3.2 内存标记(Mark) 3.3 三色标记 3.4 Stop The World 4. 垃圾回收优化 4.1 ...
- .NET垃圾回收 – 原理浅析
在开发.NET程序过程中,由于CLR中的垃圾回收(garbage collection)机制会管理已分配的对象,所以程序员就可以不用关注对象什么时候释放内存空间了.但是,了解垃圾回收机制还是很有必要的 ...
- .net垃圾回收-原理浅析
本文引自:http://www.cnblogs.com/wilber2013/p/4357910.html 在开发.NET程序过程中,由于CLR中的垃圾回收(garbage collection)机制 ...
- Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收
很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...
- JVM内存管理及垃圾回收【转】
很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...
随机推荐
- 经验总结之 _DEBUGGER _03 _Server Tomcat v8.0 Server at localhost was unable to start within xx seconds
经验总结之 _DEBUGGER _03 _Server Tomcat v8.0 Server at localhost was unable to start within xx seconds 好好 ...
- 关闭火狐Firefox下载提示弹窗
关闭火狐 Firefox 下载提示弹窗可以通过地址栏输入about:config,打开高级首选项,搜索:browser.download.alwaysOpenPanel将true改为false即可关闭 ...
- SQL Server 安装图解
此安装步骤适用于首次安装 一.SQL Server 安装 1.双击SQL Server 光盘映像文件,会出现如图所示界面 选择如下图所示的选项 2.输入产品密钥(这里演示密钥进行) 3.配置更新项,检 ...
- 共享存储ISCSI
建立共享iscsi磁盘组 资源环境 服务端:192.168.2.131 客户端:192.168.2.[110,169] 服务端磁盘: [root@centos ~]# lsblk NAME MAJ:M ...
- 【一步步开发AI运动小程序】七、进行运动计时、计数
随着人工智能技术的不断发展,阿里体育等IT大厂,推出的"乐动力"."天天跳绳"AI运动APP,让云上运动会.线上运动会.健身打卡.AI体育指导等概念空前火热.那 ...
- Apache+JK+Tomcat 负载平衡配置
网 上关于 Apache + JK + Tomcat 的集群配置例子很多,按着例子配置下来,基本都能运行,不过,在一些重要的地方却没有进一步的说明.这次公司一个产品就是采用Apache+JK+Tomc ...
- ssh之秘钥登陆
前提: 1. 秘钥的生成需要OpenSSL的支持, 需要自行进行安装 一. 新建用户 在root登陆状态中执行命令: useradd -m ssh-user # centosadduser ssh-u ...
- Blazor 组件库 BootstrapBlazor 中Message组件介绍
组件介绍 上一篇文章我们介绍了Alert组件,但是实际上Alert组件只能直接把内容加载到页面内,这个做不到当作提示使用. 所以我们还有一个新的组件Message.这个组件的样式几乎与Alert组件一 ...
- springboot 前后端大打包成一个JAR
1.概述 现在开发使用前后端开发机制,在部署的时候,我们需要将前后端分别打包,使用nginx 进行统一部署.这样就比较复杂,我们可以使用前后端打包到一个jar中,这样我们只需要一个包就可以了. 2.实 ...
- 用谷歌经典ML方法方法来设计生成式人工智能语言模型
上一篇:<人工智能模型学习到的知识是怎样的一种存在?> 序言:在接下来的几篇中,我们将学习如何利用 TensorFlow 来生成文本.需要注意的是,我们这里并不使用当前最热门的 Trans ...