G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数 -XX:G1HeapRegionSize=n 可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
大于G1中region大小50%的对象
当线程为巨型分配空间时,不能简单在TLAB进行分配,因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区(Humongous Region)。G1内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。
巨型对象会独占一个、或多个连续分区,其中第一个分区被标记为开始巨型(StartsHumongous),相邻连续分区被标记为连续巨型(ContinuesHumongous)。由于无法享受Lab带来的优化,并且确定一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。
频繁大型对象分配会导致性能问题。如果region里面包含大量的大型对象,则该region中最后一个具有巨型对象的区域与区域末端之间的空间将不会使用。如果有多个这样的大型对象,这个未使用的空间可能导致堆碎片化。直到jdk1.8u40之前,这些巨型对象的回收只在full GC期间完成。在较新的JVM中,对这些对象的清理放在了清理阶段。
在每个分区内部又被分成了若干个大小为512 Byte卡片(Card),标识堆内存最小可用粒度所有分区的卡片将会记录在**全局卡片表(Global Card Table)**中,分配的对象会占用物理上连续的若干个卡片,当查找对分区内对象的引用时便可通过记录卡片来查找该引用对象(见RSet)。每次对内存的回收,都是对指定分区的卡片进行处理。
已记忆集合 Remember Set (RSet):一个谁引用了我的机制,记录引用了当前region的分区内对象的卡片索引,当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,来确定本分区内的对象存活情况,如被引用的分区内对象全是垃圾了,则当前分区内的对象可能也是垃圾了。
Remembered Set是辅助GC过程的一种结构,典型的空间换时间工具,和Card Table有些类似。还有一种数据结构也是辅助GC的:Collection Set(CSet),它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。
逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。 这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。RSet究竟是怎么辅助GC的呢?在做YGC的时候,只需要选定young generation region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation。 而mixed gc的时候,old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量。
一组可被回收的分区的集合。在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自Eden空间、survivor空间、或者老年代。CSet会占用不到整个堆空间的1%大小。
SATB(Snapshot At The Beginning)的作用是保证在并发标记阶段的正确性。
G1会使用pre-write barrier来记录你要修改的ref的当前值,当前值会存储在satb_mark_queue中,这就像对gc开始的时候进行了一个快照。
prevBitmap、nextBitmap
本地分配缓冲 Local allocation buffer (Lab)
值得注意的是,由于分区的思想,每个线程均可以"认领"某个分区用于线程本地的内存分配,而不需要顾及分区是否连续。因此,每个应用线程和GC线程都会独立的使用分区,进而减少同步时间,提升GC效率,这个分区称为本地分配缓冲区(Lab)。
其中,应用线程可以独占一个本地缓冲区(TLAB)来创建的对象,而大部分都会落入Eden区域(巨型对象或分配失败除外),因此TLAB的分区属于Eden空间;而每次垃圾收集时,每个GC线程同样可以独占一个本地缓冲区(GCLAB)用来转移对象,每次回收会将对象复制到Suvivor空间或老年代空间;对于从Eden/Survivor空间晋升(Promotion)到Survivor/老年代空间的对象,同样有GC独占的本地缓冲区进行操作,该部分称为晋升本地缓冲区(PLAB)。
- 虚拟机栈栈帧中的变量引用的对象,扫描栈时,hotspot使用oopmap加速扫描,而不用遍历每个栈帧
- 类的常量,如果类在,这个常量引用的对象就得在
- 静态变量引用的对象
- 本地方法栈中引用的对象
OopMap 记录了栈上本地变量到堆上对象的引用关系。其作用是:垃圾收集时,收集线程会对栈上的内存进行扫描,看看哪些位置存储了 Reference 类型。
参考 JVM 之 OopMap 和 RememberedSet
垃圾收集器(Collecotr)和应用程序(Mutator)
转移失败的担保机制 Full GC
转移失败(Evacuation Failure)是指当G1无法在堆空间中申请新的分区时,G1便会触发担保机制,执行一次STW式的、单线程的Full GC。Full GC会对整堆做标记清除和压缩,最后将只包含纯粹的存活对象。参数-XX:G1ReservePercent(默认10%)可以保留空间,来应对晋升模式下的异常情况,最大占用整堆50%,更大也无意义。
2.1 G1 Evacuation Pause
-XX:MetaspaceSize=128m
由于元数据空间不足导致的GC.原来在G1中设置PermSize(永久代大小)已经没用了,取而代之的是MetaSpace参数,虽然这个用的是并非堆内存而是机器的物理内存并且最大大小没有限制,但是默认初始大小只有20m
https://blog.csdn.net/qq_27529917/article/details/87072130
-XX:+UseG1GC 使用G1垃圾回收器
-XX:MaxGCPauseMillis:暂停时间,默认值200ms
用-XX:MaxGCPauseMillis来指定,默认值200ms。这是一个软性目标,G1会尽量达成,如果达不成,会逐渐做自我调整。对于Young GC来说,会逐渐减少Eden区个数,减少Eden空间那么Young GC的处理时间就会相应减少;对于Mixed GC,G1会调整每次Choose Cset的比例,默认最大值是10%,当然每次选择的Cset少了,所要经历的Mixed GC的次数会相应增加。同时减少Eden的总空间时,就会更加频繁的触发Young GC,也就是会加快Mixed GC的执行频率,因为Mixed GC是由Young GC触发的,或者说借机同时执行的。频繁GC会对对应用的吞吐量造成影响,每次Mixed GC回收时间太短,回收的垃圾量太少,可能最后GC的垃圾清理速度赶不上应用产生的速度,那么可能会造成串行的Full GC,这是要极力避免的。所以暂停时间肯定不是设置的越小越好,当然也不能设置的偏大,转而指望G1自己会尽快的处理,这样可能会导致一次全部并发标记后触发的Mixed GC次数变少,但每次的时间变长,STW时间变长,对应用的影响更加明显。
-XX:G1HeapRegionSize:Region大小
若未指定则默认最多生成2048块,每块的大小需要为2的幂次方,如1,2,4,8,16,32,最大值为32M。Region的大小主要是关系到Humongous Object的判定,当一个对象超过Region大小的一半时,则为巨型对象,那么其会至少独占一个Region,如果一个放不下,会占用连续的多个Region。当一个Humongous Region放入了一个巨型对象,可能还有不少剩余空间,但是不能用于存放其他对象,这些空间就浪费了。所以如果应用里有很多大小差不多的巨型对象,可以适当调整Region的大小,尽量让他们以普通对象的形式分配,合理利用Region空间。
-XX:G1NewSizePercent -XX:G1MaxNewSizePercent:新生代比例
XX:G1MaxNewSizePercent,默认值60%。G1会根据实际的GC情况(主要是暂停时间)来动态的调整新生代的大小,主要是Eden Region的个数。最好是Eden的空间大一点,毕竟Young GC的频率更大,大的Eden空间能够降低Young GC的发生次数。但是Mixed GC是伴随着Young GC一起的,如果暂停时间短,那么需要更加频繁的Young GC,同时也需要平衡好Mixed GC中新生代和老年代的Region,因为新生代的所有Region都会被回收,如果Eden很大,那么留给老年代回收空间就不多了,最后可能会导致Full GC。
-XX:ParallelGCThreads:并行GC线程数
通过 -XX:ParallelGCThreads来指定,也就是在STW阶段工作的GC线程数,其值遵循以下原则:
① 如果用户显示指定了ParallelGCThreads,则使用用户指定的值。
② 否则,需要根据实际的CPU所能够支持的线程数来计算ParallelGCThreads的值,计算方法见步骤③和步骤④。
③ 如果物理CPU所能够支持线程数小于8,则ParallelGCThreads的值为CPU所支持的线程数。这里的阀值为8,是因为JVM中调用nof_parallel_worker_threads接口所传入的switch_pt的值均为8。
④ 如果物理CPU所能够支持线程数大于8,则ParallelGCThreads的值为8加上一个调整值,调整值的计算方式为:物理CPU所支持的线程数减去8所得值的5/8或者5/16,JVM会根据实际的情况来选择具体是乘以5/8还是5/16。
比如,在64线程的x86 CPU上,如果用户未指定ParallelGCThreads的值,则默认的计算方式为:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。线程名:Gang worker#0 (Parallel GC Threads)
-XX:ConcGCThreads:并发GC线程数
通过 -XX:ConcGCThreads来指定,默认是-XX:ParallelGCThreads/4,也就是在非STW期间的GC工作线程数,当然其他的线程很多工作在应用上。当并发周期时间过长时,可以尝试调大GC工作线程数,但是这也意味着此期间应用所占的线程数减少,会对吞吐量有一定影响。
线程名:Gang worker#0 (G1 Parallel Marking Threads)
-XX:G1MixedGCLiveThresholdPercent:被纳入Cset的Region的存活空间占比阈值
通过 -XX:G1MixedGCLiveThresholdPercent指定,不同版本默认值不同,有65%和85%。在全局并发标记阶段,如果一个Region的存活对象的空间占比低于此值,则会被纳入Cset。此值直接影响到Mixed GC选择回收的区域,当发现GC时间较长时,可以尝试调低此阈值,尽量优先选择回收垃圾占比高的Region,但此举也可能导致垃圾回收的不够彻底,最终触发Full GC。
-XX:InitiatingHeapOccupancyPercent:触发全局并发标记的老年代使用占比,默认值45%
通过-XX:InitiatingHeapOccupancyPercent指定,默认值45%,也就是老年代占堆的比例超过45%。如果Mixed GC周期结束后老年代使用率还是超过45%,那么会再次触发全局并发标记过程,这样就会导致频繁的老年代GC,影响应用吞吐量。同时老年代空间不大,Mixed GC回收的空间肯定是偏少的。可以适当调高IHOP的值,当然如果此值太高,很容易导致年轻代晋升失败而出发Full GC,所以需要多次调整测试。
或者:如果整个堆的使用率超过这个值,G1会触发一次并发周期。记住这里针对的是整个堆空间的比例,而不是某个分代的比例。
-XX:G1HeapWastePercent:触发Mixed GC的堆垃圾占比,默认值5%
通过-XX:G1HeapWastePercent指定,默认值5%,也就是在全局标记结束后能够统计出所有Cset内可被回收的垃圾占整个堆的比例值,如果超过5%,那么就会触发之后的多轮Mixed GC,如果不超过,那么会在之后的某次Young GC中重新执行全局并发标记。可以尝试适当的调高此阈值,能够适当的降低Mixed GC的频率。
-XX:G1OldCSetRegionThresholdPercent:每轮Mixed GC回收的Region最大比例,默认10%
通过-XX:G1OldCSetRegionThresholdPercent指定,默认10%,也就是每轮Mixed GC附加的Cset的Region不超过全部Region的10%,最多10%,如果暂停时间短,那么可能会少于10%。一般这个值不需要额外调整。
-XX:G1MixedGCCountTarget:一个周期内触发Mixed GC最大次数,默认值8
通过-XX:G1MixedGCCountTarget指定,默认值8。也就是在一次全局并发标记后,最多接着8次Mixed GC,也就是会把全局并发标记阶段生成的Cset里的Region拆分为最多8部分,然后在每轮Mixed GC里收集一部分。这个值要和上一个参数配合使用,8*10%=80%,应该来说会大于每次标记阶段的Cset集合了。一般此参数也不需额外调整。
-XX:G1ReservePercent:G1为分配担保预留的空间比例
通过-XX:G1ReservePercent指定,默认10%。也就是老年代会预留10%的空间来给新生代的对象晋升,如果经常发生新生代晋升失败而导致Full GC,那么可以适当调高此阈值。但是调高此值同时也意味着降低了老年代的实际可用空间。
-XX:SoftRefLRUPolicyMSPerMB:谨慎使用Soft Reference,每兆堆空闲空间的软引用的存活时间
如果SoftReference过多,会有频繁的老年代收集。-XX:SoftRefLRUPolicyMSPerMB参数,可以指定每兆堆空闲空间的软引用的存活时间,默认值是1000,也就是1秒。可以调低这个参数来触发更早的回收软引用。如果调高的话会有更多的存活数据,可能在GC后堆占用空间比会增加。 对于软引用,还是建议尽量少用,会增加存活数据量,增加GC的处理时间。
-XX:MaxTenuringThreshold:晋升年龄阈值,默认值15
通过-XX:MaxTenuringThreshold指定,默认值15。一般新生对象经过15次Young GC会晋升到老年代,巨型对象会直接分配在老年代,同时在Young GC时,如果相同age的对象占Survivors空间的比例超过 -XX:TargetSurvivorRatio的值(默认50%),则会自动将此次晋升年龄阈值设置为此age的值,所有年龄超过此值的对象都会被晋升到老年代,此举可能会导致老年代需要不少空间应对此种晋升。一般这个值不需要额外调整。
-XX:G1ConcRefinementThreads:和RSet相关
https://www.jianshu.com/p/870abddaba41
-XX:+PrintAdaptiveSizePolicy: 显示收集器工效(Collector ergonomics)
-XX:+PrintTenuringDistribution: Survivor区的使用和分布
-XX:+DisableExplicitGC 关闭System.gc()
-XX:+PrintGCApplicationConcurrentTime
java -server -XX:MaxPermSize=512m -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintGCDateStamps -Xloggc:/opt/wildfly/standalone/log/verbose.gc -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/wildfly/standalone/log/ -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -Dcom.ibm.mq.cfg.TCP.Connect_Timeout=20 -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=20M -Xms4096m -Xmx4096m -XX:+UseG1GC
GC类型 | GC触发条件 | 回收对象 |
---|---|---|
young gc | Eden区满了,不能再分配新的对象时,触发young gc | 所有年轻代的Region |
mixed gc | young gc时,可能触发mixed gc,条件:-XX:G1HeapWastePercent | 回收所有的年轻代的Region+部分老年代的Region |
full gc | 1 Evacuation的时候没有足够的to-space来存放晋升的对象; 2 并发处理过程完成之前空间耗尽 |
整个内存堆 |
全局标记 | -XX:InitiatingHeapOccupancyPercent:触发全局并发标记的老年代使用占比, 伴随一个young gc一起执行。 | |
Gang worker#0 (Parallel GC Threads) :-XX:ParallelGCThreads
Gang worker#0 (G1 Parallel Marking Threads):-XX:ConcGCThreads
G1 Main Concurrent Mark GC Thread: 主线程,就一个
G1 Concurrent Refinement Thread#0:个数又-XX:G1ConcRefinementThreads=2 指定,如果=2, 则有3个线程(n+1)。
G1在以下场景中会触发Full GC,同时会在日志中记录to-space-exhausted以及Evacuation Failure:
1 由于并发进行,CMS在收集与应用线程会同时会增加对堆内存的占用,也就是说,CMS必须要在老年代堆内存用尽之前完成垃圾回收,否则CMS回收失败时,将触发担保机制,串行老年代收集器将会以STW的方式进行一次GC,从而造成较大停顿时间;
2 标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐步耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS也提供了参数-XX:CMSFullGCsBeForeCompaction(默认0,即每次都进行内存整理)来指定多少次CMS收集之后,进行一次压缩的Full GC。
G1从入门到放弃(一)
G1从入门到放弃(二)
Understanding G1 GC Logs
理解G1垃圾收集器日志
JVM性能调优实践——G1 垃圾收集器介绍篇
JVM性能调优实践——G1 垃圾收集器分析、调优篇
G1垃圾收集器之RSet
Understanding G1 GC Log Format
Part 1: Introduction to the G1 Garbage Collector
Collecting and reading G1 garbage collector logs - part 2
G1 GC log的解读
带你读《新一代垃圾回收器ZGC设计与实现》之三:ZGC线程
深入理解G1垃圾收集器 描述了G1的回收过程
可能是最全面的G1学习笔记 确实比较全
弄明白CMS和G1,就靠这一篇了 论述了CMS和G1
G1摘要 概念,日志分析
详解 JVM Garbage First(G1) 垃圾收集器大而全,但是感觉有描述不准确的地方
一次非典型的CPU告警的排查
G1垃圾回收器GC频繁导致的系统波动问题