深刻了解JVM - G1收集器
前言
上一篇通过案例阐明了老年代的常见优化和解决形式,这一节来看下目前最为热门的G1收集器,G1收集器也是JDK9服务端默认的垃圾收集器,尽管JDK9在当初看来还不是非常的遍及,然而学习这个垃圾收集器是非常重要也是十分必要的。
前文回顾
上一节咱们通过一个电商的模仿实战,理解了老年代常见的优化形式。同时在最初总结了老年代优化的一些常见的套路,上面间接用上一节的总结回顾整个内容:
- 首先业务的对象都是生命周期非常短暂的对象,新生代的压力比老年代要大,所以适当放大老年代空间是非常划算的- 预测在高并发的场景下对象进入老年代的机会,如果对象常常“跨区”阐明有一部分内容空间是节约了,那就是Survior区域- 对象在各分区须要大抵多少的内存空间,比方每个线程须要占用多少的内存空间- 对象的年龄判断是否须要改变,提前让对象进入老年代是益处还是害处- 关注收集器对于对象垃圾回收的影响,同时在启动的时候要强制应用某一垃圾收集器,因为不同的JDK版本默认的垃圾收集器是不一样的。
CMS+ParNew收集器的痛点是什么?
在之前的文章咱们提到过,传统的黄金组合的痛点Stop world (世界进展):在并发标记和并发整顿阶段如果呈现调配对象超过老年代代而导致Full Gc,会立即停下手头的所有工作全力进行垃圾收集的动作,并且应用Serial old收集器解决,这对于用户来说会发现显著的卡顿,同时会误认为零碎卡死,体验非常差。
G1收集器的介绍
为什么会诞生G1?
感兴趣能够看下对于G1论文的白皮书:http://citeseerx.ist.psu.edu/...。(看评论说其实讲的非常形象和抽象,倡议深入研究G1的大抵看看)
为什么会诞生G1呢?最大起因是JVM目前不能让收集器的垃圾回收进展在某个特定工夫范畴,然而也不须要像实时那样要求非常的严格。基于这样的思考,早在06年就呈现了G1的论文,然而最终实现花了好多年的工夫!能够设想这个收集器外部注定是非常复杂的,当然咱们没有必要去深入研究底层,只须要他的根底原理即可。
历史进程:
- Jdk7 update4被商用
- jdk8 update40 并发类型卸载反对
- Jdk9成为服务端默认垃圾收集器
- Jdk10因为cms垃圾收集器插件性能耦合等缘故,重构了“对立垃圾回收接口”
G1收集器的特点
- 设置一个垃圾的预期进展工夫。依据Region的大小和回收价值进行最有效率的回收。
- 内存不再固定划分新生代和老年代,应用Region对于内存进行分块,实现了依据系统资源动静分代。
- Region可能属于新生代或者老年代,同时调配给新生代还是老年代是由G1本人管制的。
- 抉择最小回收工夫以及最多回收对象的region进行垃圾的回收操作。
补充《深刻了解JVM虚拟机》介绍的特点:
- 不再保持固定大小以及固定数量的分区划分
- 应用region划分区域,大小相等,不同区域表演不同的角色
- humongous区域:设计被用于大对象应用,依据最短进展工夫模型优先回收价值最高的region。超过一个region一半的大小都为大对象
- 后盾应用一个优先级列表优先回收收益最大的区域
G1的Region
G1勾销了固定分代的概念,取而代之的是应用分区的概念,把内存切分成一个个小块,同时各个小块又分为新生代(eden、Survior区)、老年代、大对象(humongous区)等等。
G1规定大于Region的一半的对象成为大对象,同时不参加分代。
Region并不是固定为新生代或者老年代,通常状况为自在状态,只有在须要的时候会被划分为指定的分代并且寄存特定对象。这里兴许会有疑难,这样调配Region不会产生很多垃圾碎片么?答案当然是并不会,首先新生代应用复制算法,会把存活对象拷贝到一块region进行寄存,同时存活对象大于肯定的region占比不会进行复制(下文会提到),把整个region清理并且进行回收,而老年代则应用标记-整顿算法,同样的情理也会间接把垃圾对象的region给清理掉,也是不会产生内存碎片的,最初,大对象是横跨多个region并由专门的region寄存,一旦回收间接干掉大对象把对应的region清理即可。
到底有多少region,每个region的大小是多少?
计算形式: 堆大小 / 2048,G1收集器最多能够有2048个region,并且大小必须是2的倍数。也就是说4G内存给每一个region的大小是2M的大小。
上面是G1常见的配置参数:
-XX:Heap Region Size:手动制订region的大小。单个region的大小指定,默认为2M,4g内存当中-XX:G1NewSizePercent: 手动指定新生代初始占比,默认是5%-XX:G1MaxNewSizePercent:新生代的最大占比默认是60%
罕用参数:
-XX:G1MixedGCCountTarget
:示意一次垃圾回收之后,最初一次混合回收执行几次。这个参数意味着在垃圾回收的最初一个阶段回收和零碎运行交替运行,并且默认回收8次之后完结这个操作。-XX:G1HeapWastePercent
:默认为5%。在混合回收的时候如果一次混合回收的闲暇region超过5%就立即进行垃圾回收的操作,这个参数也是为了尽量减少进展的工夫设计的。-XX:G1MixedGCLiveThreasholdPercent
:默认值为85%,示意存活对象要低于85%才进行回收的操作。因为如果一个region的存活对象大于85%,复制拷贝的代价是非常大的,并且这一类region大概率进入老年代-XX:G1MaxNewSizePercent
:新生代最大占用堆内存空间,默认值为60%。这个参数意味着在初始5%的新生代状况下,零碎运行最多能够从零碎总空间调配60%给新生代应用,超过这个值就必定会触发新生代回收。-XX:NewRatio=n
:配置新生代与老年代的比例,默认是2:1
如何了解G1的工作模式?
为了更好了解G1的工作模式进展工夫模型,这里集体想了一个还算活泼的例子解释下G1的工作模式:
咱们平时去服务比拟周到的餐馆,服务员通常会看咱们桌子上垃圾的量有多少或者吃剩的盘子有多少,当垃圾超过一定量之后,服务员就会过去开盘子或者收垃圾,而后不便前面上菜,同时也能够让洗盘子的服务员忙忙停停的工作。
这之后就会思考,如果每多出一个盘子就去收一个,你会不会很累,会不会等到盘子有一定量了再去收,比方桌子摆不下新的菜了,或者盘子叠了很多个。
最初,如果每次开盘子的速度能赶上上菜的速度,那么根本的失常运行是没有大问题的。
G1适宜什么样的零碎?
G1适宜的零碎包含超大内存的零碎,此时如果依照传统的分代,比方16G的内存新生代8G,老年代8G这种划分,尽管拉长垃圾进展的工夫,然而一旦新生代被占满,回收的效率是非常低的,因为对象和GC ROOT十分多,最终导致垃圾收集长时间卡顿。而G1不一样,他只须要依照算法判断依据进展模型的值在新生代靠近进展模式工夫的时候就马上开启回收,不必等新生代满了才回收。
G1也适宜须要低提早的零碎,因为低提早对于零碎的响应要求是十分高的,更加看重响应的工夫,同时对于零碎的资源要求也比拟高,而分代的模型和实践在大内存机器会造成长时间的垃圾收集进展,这对于实时响应的服务要求是十分高的。
G1比照Parnew+cms最大的提高在哪里?
G1最大的提高也是他的特点就是 工夫进展模型, 能够管制stop world的工夫。同时能够让垃圾收集的工夫管制在预期设置的范畴之内。
其余提高:
- 算法: G1基于标记-整顿算法, 不会产生空间碎片,调配大对象时不会无奈失去间断的空间而提前触发一次FULL GC。
- 多线程:多线程算法比CMS更加优良,同时更能施展多核性能
G1没有毛病了?
没毛病是不可能的,必定是有毛病,这个收集器最大的问题恰好在于进展模型,外面的算法细节非常的简单,所以咱们调试不能仅仅通过业务推算,而是要依据日志以及工具辅助来实现调优,G1的调优是十分麻烦的一件事。
其次就是Region的设计了,region的设计自身要耗费大量的系统资源进行保护的,这意味着G1收集器自身至多须要占用10%-20%的内存来维持本人的失常工作,比方计算进展模型,维持跨代援用和GC ROOT等信息,这两头蕴藏的细节十分复杂,这里的内容能够参考【其余材料-干货文章】这部分理解,所以G1收集器自身就须要较好的机器性能才倡议应用。
上面是依据《深刻了解JVM虚拟机》书中总结G1的毛病:
- 每个region要更大的内存空间解决记忆集的耗费问题,还须要tams耗费的局部内存实现快照的性能。
- cms应用写后屏障,而G1不仅应用写后屏障还用了写前屏障。额定应用队列进行异步并发标记的对象指针改变的问题(最终标记的工夫开销)
- 因为cms应用卡集的形式增量更新,只须要写屏障的卡表援用,但会导致用户线程暂停.
G1的垃圾回收步骤
初始标记:
和cms垃圾收集器相似,在初始的的状况须要stop the world的操作。仅仅标记GC ROOT 能够援用的对象,整个过程十分快。
这里能够看到对空间的构造曾经和传统固定的分代模型曾经不一样了,堆内存被划分为小块的region,初始标记会依据栈中的局部变量援用或者传递援用等标记初始的GC ROOT对象,这个过程能够比拟快的实现,因为仅仅是简略标记而已。
并发标记:
这个阶段也和cms比拟相似,同样能够和用户线程一起并发运行,零碎能够失常调配对象,而垃圾回收线程依据GC ROOT 进行对象的援用,标记存活对象。同时将对象的改变进行标记记录,这个阶段会比拟消耗系统资源,然而和零碎线程一起并发影响也不是很大。
最终标记:
最终标记阶段:这个阶段和CMS相似,也是须要stop world,此时零碎过程须要暂停,进行对象的调配,同时垃圾收集器负责对于对象进行最初的标记和分类动作,决定哪些对象须要被垃圾回收。
筛选回收:
筛选回收阶段:须要重点记忆的一个阶段。这个阶段会计算老年代有多少个region存活,存活对象的占比,以及回收的效率计算。
这个阶段也是须要stop world,会让垃圾收集线程马力全开,在指定的进展工夫范畴内实现更多的垃圾回收动作。同时这个阶段会反复屡次,并且在回收的时候和零碎线程是交替运行的,也就是说回收的时候须要暂停。
另外回收阶段不仅回收老年代,还回收新生代以及大对象,算是真正意义上的full GC。比方:老年代有1000个region,而通过计算发现须要回收的region为800个,那么就会回收800个REGION
这里再联合步骤阐明一下下面的局部参数的作用,从上图能够看到,零碎线程和垃圾回收是交替运行的,在最初一步默认会让零碎和垃圾收集线程交互运行8次,假如进展工夫设置为200ms,那么就是每次在200ms内尽可能回收垃圾,回收实现立马开启零碎线程,而后运行一段时间又进行回收,如此重复,如果在垃圾回收中4次就回收掉超过5%,那么意味着这一个阶段提前完成了,就会立马进入下一次的垃圾回收循环。
对应回收参数:
-XX:G1MixedGCCountTarget
:示意一次垃圾回收之后,最初一次混合回收执行几次。这个参数意味着在垃圾回收的最初一个阶段回收和零碎运行交替运行,并且回收8次之后完结这个操作。-XX:G1HeapWastePercent
:默认为5%。在混合回收的时候如果一次混合回收的闲暇region超过5%就立即进行垃圾回收的操作,这个参数也是为了尽量减少进展的工夫设计的。
G1的FAQ
G1在什么时候会触发Mixed GC
参数:-XX:InterfaceTestControllernitiatingHeapOccupancyPercent
,他的默认值是45%。
意思就是说如果老年代占用超过了45%,就会触发一个叫做混合回收的操作,混合回收意味着新生代和老年代一起回收,这时候毫无疑问整个零碎线程都会进行。
回收失败了如何解决?
回收失败了,就会进行线程,而后通过单线程的serrial形式对于所有region存活对象标记,整顿,而后清理垃圾对象,整个过程是十分迟缓的。
这个过程其实和CMS的Corrurnet mode fail相似,然而因为G1的内存模型齐全另辟蹊径,所以他须要回收新生代,老年代和大对象,算法的细节要更为简单,整顿复原的工夫也比拟长,也能够说G1的回收是真正意义上的Full GC。
G1还存在eden区域和survior区域么?
答案是尽管不须要指定新生代和老年代的大小由G1管制,然而region自身在运行时还是会划分新生代或者老年代,只是不再是固定的了,所以新生代还是有对应的Eden和Survior区域。
G1的新生代是如何回收的?
还是采纳复制算法,当新生代超过默认的60%的最大占比限度的时候,会触发Minor gc,而后进行stop world的操作。
这里可能会想这不还是和之前没区别么?之前说过G1的特点是指定垃圾回收的最大进展工夫。G1会依据Region的大小和回收预测工夫在咱们指定的最大回收工夫内尽可能的回收内存,回收之后的内存能够给新生代应用也能够给老年代应用。
G1的老年代是如何回收的?
这里须要留神,G1自身曾经没有老年代回收这个概念了,取而代之的是Mixed Gc也就是混合回收,当老年代的region占比超过45%就会触发。
G1的回收和之前分代的垃圾收集器有什么区别?
首先须要弄清一个概念,就是region尽管是分代存储的然而并不代表始终是分代的region,比方新生代如果有600个region,回收了200个region,这200个region等于是“自在”的,能够分给新生代也能够给老年代应用。
G1的进展工夫模型会依据用户设置的比方200MS内进行回收操作,能够通过:-XX:MaxGCPauseMills 这个参数设置最大的进展等待时间,g1会依据此参数追踪最有回收价值的region进行解决,然而这个参数其实是一个软指标,并不是说收集器齐全保障这个时间段内实现回收,而是意味着在这个时间段内做最有价值的回收,就像前文提到的开盘子的概念一样,他会尽可能的解决让垃圾收集速度更上调配的速度。
对象什么时候会进入老年代?
- 对象在新生代躲过了很屡次垃圾回收,达到肯定的对象年龄,-XX:MaxTenuringThreashold这个参数能够设置年龄。
- 依据Survior总体存活率超过50%的状况判断,当排序发现某一年龄对象大小超过survior区域的50%会触发。
大对象什么时候回收?
大对象不属于新生代也不属于老年代,所以在新生代或者老年代回收的时候会顺带回收大对象的region内存。也就是说大对象的回收依附Mixed回收。
其余材料:
算法细节摘录
本人看书的一些笔记,不适宜作为注释,所以放到最初(反正也没人看,哈哈)
Region应用两个ams指针把region的一部分空间划分对象调配应用,所有新调配的对象存在此区间,同样如果回收赶不上内存调配,也要解冻用户线程,进行stop world。
可预测模型依附:-XX:MaxGcRauseMillion 期望值,G1会在期望值内做出最有效率的回收
算法:
G1应用衰减均值算法,垃圾回收计算region回收耗时和脏卡数据
- 参照值:平均值,标准偏差,置信度信息
- 默认200毫秒进展工夫,低于这个值很可能垃圾回收速度赶不上调配对象速度
干货文章:
知乎:G1 收集器原理了解与剖析
求教G1算法的原理
美团:Java Hotspot G1 GC的一些关键技术
写在最初:
最初吐槽一句,JVM真的很难,光看《深刻了解JVM虚拟机》这本书也只能大略理解目前支流收集器实现的大抵原理,如果要深究须要长时间的积攒,当然咱们不须要学的那么苦楚,这篇文章讲到的内容根本能应酬80、90%的场景了。
下一篇文章依据一个案例讲一下G1的大抵优化思路,留神只是大抵思路,和之前的分代收集器不同,G1要真刀真枪的去跑工具,看日志,做数据分析能力调优好,因为能用上G1收集器的零碎少数不会小,小零碎用用分代并且并发访问量不大的也不须要怎么调优。