深⼊理解JVM垃圾收集机制(JDK1.8)
垃圾收集算法
标记-清除算法
最基础的收集算法是“标记-清除”(Mark-Sweep)算法,分两个阶段:⾸先标记出所有需要回收的对象,在标记完成后统⼀回收所有被标记的对象。
不⾜:⼀个是效率问题,标记和清除两个过程的效率都不⾼;另⼀个是空间问题,标记清除之后会产⽣⼤量不连续的内存碎⽚,空间碎⽚太多可能导致以后在程序运⾏过程需要分配较⼤对象时,⽆法到⾜够的连续内存⽽不得不提前触发另⼀个的垃圾收集动作。
复制算法
为了解决效率问题,⼀种称为复制(Copying)的收集算法出现了,它将可⽤内存按容量划分为⼤⼩相等的两块,每次只使⽤其中的⼀块。当这⼀块内存⽤完了,就将还存活着的对象复制到另外⼀块上,然后再把已经使⽤过的内存空间⼀次清理掉。这样使得每次都是对整个半区进⾏内存回收,内存分配时也就不⽤考虑内存碎⽚等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运⾏⾼效。代价是内存缩⼩为原来的⼀半。
商业虚拟机⽤这个回收算法来回收新⽣代。IBM研究表明98%的对象是“朝⽣⼣死“,不需要按照1-1的⽐例来划分内存空间,⽽是将内存分为⼀块较⼤的”Eden“空间和两块较⼩的Survivor空间,每次使⽤Eden和其中⼀块Survivor。当回收时,将Eden和Survivor中还存活的对象⼀次性复制到另外⼀个Survivor空间上,最后清理掉Eden和刚才⽤过的Survivor空间。Hotspot虚拟机默认Eden和Survivor的⽐例是8-1.即每次可⽤整个新⽣代的90%, 只有⼀个survivor,即1/10被”浪费“。当然,98%的对象回收只是⼀般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够时,需要依赖其他内存(⽼年代)进⾏分配担保(Handle Promotion).
如果另外⼀块survivor空间没有⾜够空间存放上⼀次新⽣代收集下来的存活对象时,这些对象将直接通过分配担保机制进⼊⽼年代。
eden survivor复制过程概述
Eden Space字⾯意思是伊甸园,对象被创建的时候⾸先放到这个区域,进⾏垃圾回收后,不能被回收的对象被放⼊到空的survivor区域。
Survivor Space幸存者区,⽤于保存在eden space内存区域中经过垃圾回收后没有被回收的对象。Survivor有两个,分别为To Survivor、 From Survivor,这个两个区域的空间⼤⼩是⼀样的。执⾏垃圾回收的时候Eden区域不能被回收的对象被放⼊到空的survivor(也就是To Survivor,同时Eden区域的内存
会在垃圾回收的过程中全部释放),另⼀个survivor(即From Survivor)⾥不能被回收的对象也会被放⼊这个survivor(即To Survivor),然后To Survivor 和 From Survivor的标记会互换,始终保证⼀个survivor是空的。
为啥需要两个survivor?因为需要⼀个完整的空间来复制过来。当满的时候晋升。每次都往标记为to的⾥⾯放,然后互换,这时from已经被清空,可以当作to了。
标记-整理算法
复制收集算法在对象成活率较⾼时就要进⾏较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进⾏分配担保,以应对被使⽤的内存中所有对象都100%存活的极端情况,所以,⽼年代⼀般不能直接选⽤这种算法。
根据⽼年代的特点,有⼈提出⼀种”标记-整理“Mark-Compact算法,标记过程仍然和标记-清除⼀样,但后续步骤不是直接对可回收对象进⾏清理,⽽是让所有存活的对象都向⼀端移动,然后直接清理端边界以外的内存.
分代收集算法
当前商业虚拟机的垃圾收集都采⽤”分代收集“(Generational Collection)算法,这种算法根据对象存活周
期的不同将内存划分为⼏块。⼀般把Java堆分为新⽣代和⽼年代,这样就可以根据各个年代的特点采⽤最适当的收集算法。在新⽣代,每次垃圾收集时都发现⼤批对象死去,只有少量存活,那就选⽤复制算法,只需要付出少量存活对象的复制成本就可以完成收集。⽽⽼年代中因为对象存活率较⾼,没有额外的空间对它进⾏分配担保,就必须使⽤”标记-清理“和”标记-整理“算法来进⾏回收。
HotSpot算法实现
在Java语⾔中,可作为GC Roots的对象包括下⾯⼏种:
虚拟机栈(栈帧中的本地变量表)中引⽤的对象
⽅法去中类静态属性引⽤的对象
⽅法区中常量引⽤的对象
本地⽅法栈中JNI(即⼀般说的Native⽅法)引⽤的对象
从可达性分析中从GC Roots节点引⽤链这个操作为例,可作为GC Roots的节点主要在全局性的引⽤(例如常量或类静态属性)与执⾏上下⽂(例如栈帧中的本地变量表)中,现在很多应⽤仅仅⽅法区就有数百兆,如果要逐个检查⾥⾯的引⽤,必然消耗很多时间。
可达性分析对执⾏时间的敏感还体现在GC停顿上,因为这项分析⼯作必须在⼀个能确保⼀致性的快照中进⾏--这⾥”⼀致性“的意思是指整个分析期间整个执⾏系统看起来就像被冻结在某个时间点,不可以出现分析过程中对象引⽤关系还在不断变化的情况,该点不满⾜的话分析结果准确性就⽆法得到保证。这点是导致GC进⾏时必须停顿所有Java执⾏线程(Sun 公司将这件事情称为”Stop The World“)的⼀个重要原因,即使是在号称(⼏乎)不会发⽣停顿的CMS收集器中,枚举根节点时也必须停顿的。
安全点,Safepoint
垃圾收集器
Serial 收集器
标记-复制。
单线程,⼀个CPU或⼀条收集线程去完成垃圾收集⼯作,收集时必须暂停其他所有的⼯作线程,直到它结束。
虽然如此,它依然是虚拟机运⾏在Client模式下的默认新⽣代收集器。简单⽽⾼效。
ParNew 收集器
ParNew是Serial收集器的多线程版本。Server模式下默认新⽣代收集器,除了Serial收集器之外,只有它能与CMS收集器配合⼯作。
并⾏ Parallel
指多条垃圾收集线程并⾏⼯作,但此时⽤户线程仍然处于等待状态。
并发 Concurrent
指⽤户线程与垃圾收集线程同时执⾏(但不⼀定是并⾏的,可能会交替执⾏),⽤户程序再继续运⾏,⽽垃圾收集程序运⾏于另⼀个CPU上。
Parallel Scavenge 收集器
Parallel Scavenge 收集器是⼀个新⽣代收集器,它也是使⽤复制算法的收集器。看上去来ParNew⼀样,有什么特别?
Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS等收集器关注点是尽可能缩短垃圾收集时⽤户线程的停顿时间。⽽Parallel Scavenge收集器的⽬标则是达到⼀个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU⽤于运⾏⽤户代码的时间和CPU总⼩号时间的⽐值,即吞吐量 = 运
⾏⽤户代码时间 / (运⾏⽤户代码时间+垃圾收集时间),虚拟机总共运⾏了100min,其中垃圾收集花费了1min,那吞吐量就是99%.
停顿时间越短就越适合需要与⽤户交互的程序,良好的响应速度能提升⽤户体验,⽽⾼吞吐量则可以⾼效地利⽤CPU时间,主要适合在后台运算⽽不需要太多交互的任务。
Parallel Scavenge收集器提供了两个参数⽤于精确控制吞吐量,分别是控制最⼤垃圾收集停顿时间 -XX:MaxGCPauseMillis以及直接设置吞吐量⼤⼩的-XX:GCTimeRatio。Serial Old收集器
Serial Old是Serial收集器的⽼年代版本,它同样是⼀个单线程收集器。给Client模式下的虚拟机使⽤。
新⽣代采⽤复制算法,暂停所有⽤户线程;
⽼年代采⽤标记-整理算法,暂停所有⽤户线程;
Parallel Old 收集器
这⾥注意,Parallel Scavage 收集器架构中本⾝有PS MarkSweep收集器来收集⽼年代,并⾮直接使⽤了Serial Old,但⼆者接近。本⼈win10 64位系统,jdk1.8.0_102,测试默认垃圾收集器为:**PS MarkSweep **和 PS Scavenge。 也就是说Java8的默认并不是G1。
这是”吞吐量优先“,注重吞吐量以及CPU资源敏感的场合都可以优先考虑Parallel Scavenge和Parallel Old(PS Mark Sweep)。Java8 默认就是这个。
CMS 收集器
CMS(Concurrent Mark Sweep) 收集器是⼀种以获取最短回收停顿时间为⽬标的收集器。⽬前很⼤⼀部分的Java应⽤集中在互联⽹站或者B/S系统的服务端上,这类尤其重视服务的响应速度,希望系统停顿时间最短。CMS收集器就⾮常符合这类应⽤的需求。
CMS基于 标记-清除算法实现。整个过程分为4个步骤:
初始标记(CMS initial mark) -stop the world
并发标记(CMS concurrent mark)
重新标记(CMS remark) -stop the world
并发清除(CMS concurrent sweep)
初始标记,重新标记这两个步骤仍然需要Stop The World, 初始标记仅仅标记以下GC Roots能直接关联的对象,速度很快。
并发标记就是进⾏GC Roots Tracing的过程;
⽽重新标记阶段则是为了修正并发标记期间因为⽤户程序继续运作⽽导致标记产⽣变动的那⼀部分对象的标记记录。这个阶段停顿⽐初始标记稍微长,但远⽐并发标记的时间短。
整个过程耗时最长的并发标记和并发清除过程,收集器都可以与⽤户线程⼀起⼯作。总体上来说,CMS收集器的内存回收过程与⽤户线程⼀起并发执⾏的。
CMS特点:并发收集,低停顿。
缺点
1.CMS收集器对CPU资源⾮常敏感。默认启动的回收线程数是(CPU+3)/4. 当CPU 4个以上时,并发回收垃圾收集线程不少于25%的CPU资源。
2.CMS收集器⽆法处理浮动垃圾(Floating Garbage), 可能出现”Concurrent Mode Failure“失败⽽导致另⼀次Full GC的产⽣。由于CMS并发清理时,⽤户线程还在运⾏,伴随产⽣新垃圾,⽽这⼀部分出现在标记之后,只能下次GC时再清理。这⼀部分垃圾就称为”浮动垃圾“。
由于CMS运⾏时还需要给⽤户空间继续运⾏,则不能等⽼年代⼏乎被填满再进⾏收集,需要预留⼀部分
空间提供并发收集时,⽤户程序运⾏。JDK1.6中,CMS启动阈值为92%. 若预留内存不够⽤户使⽤,则出现⼀次Concurent Mode Failure失败。这时虚拟机启动后备预案,临时启⽤Serial Old收集⽼年代,这样停顿时间很长。
3.CMS基于”标记-清除“算法实现的,则会产⽣⼤量空间碎⽚,空间碎⽚过多时,没有连续空间分配给⼤对象,不得不提前触发⼀次FUll GC。当然可以开启-
XX:+UseCMSCompactAtFullCollection(默认开),在CMS顶不住要FullGC时开启内存碎⽚合并整理过程。内存整理过程是⽆法并发的,空间碎⽚问题没了,但停顿时间变长。
⾯试题:CMS⼀共会有⼏次STW
⾸先,回答两次,初始标记和重新标记需要。
然后,CMS并发的代价是预留空间给⽤户,预留不⾜的时候触发FUllGC,这时Serail Old会STW.
然后,CMS是标记-清除算法,导致空间碎⽚,则没有连续空间分配⼤对象时,FUllGC, ⽽FUllGC会开始碎⽚整理, STW.
即2次或多次。
CMS什么时候FUll GC
除直接调⽤外,触发Full GC执⾏的情况有如下四种。
1. 旧⽣代空间不⾜
旧⽣代空间只有在新⽣代对象转⼊及创建为⼤对象、⼤数组时才会出现不⾜的现象,当执⾏Full GC后空间仍然不⾜,则抛出如下错误:
java.lang.OutOfMemoryError: Java heap space
为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新⽣代多存活⼀段时间及不要创建过⼤的对象及数组。
2. Permanet Generation空间满
PermanetGeneration中存放的为⼀些class的信息等,当系统中要加载的类、反射的类和调⽤的⽅法较多时,Permanet Generation可能会被占满,在未配置为采⽤CMS GC的情况下会执⾏Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
java.lang.OutOfMemoryError: PermGen space
为避免Perm Gen占满造成Full GC现象,可采⽤的⽅法为增⼤Perm Gen空间或转为使⽤CMS GC。
3. CMS GC时出现promotion failed和concurrent mode failure
jvm调优参数对于采⽤CMS进⾏旧⽣代GC的程序⽽⾔,尤其要注意GC⽇志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。promotionfailed是在进⾏Minor GC时,survivor space放不下、对象只能放⼊旧⽣代,⽽此时旧⽣代也放不下造成的;concurrent mode failure是在执⾏CMS GC的过程中同时有对象要放⼊旧⽣代,⽽此时旧⽣代空间不⾜造成的。
应对措施为:增⼤survivorspace、旧⽣代空间或调低触发并发GC的⽐率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。
4. 统计得到的Minor GC晋升到旧⽣代的平均⼤⼩⼤于旧⽣代的剩余空间
这是⼀个较为复杂的触发情况,Hotspot为了避免由于新⽣代对象晋升到旧⽣代导致旧⽣代空间不⾜的现象,在进⾏Minor GC时,做了⼀个判断,如果之前统计所得到的Minor GC 晋升到旧⽣代的平均⼤⼩⼤于旧⽣代的剩余空间,那么就直接触发Full GC。
例如程序第⼀次触发MinorGC后,有6MB的对象晋升到旧⽣代,那么当下⼀次Minor GC发⽣时,⾸先检
查旧⽣代的剩余空间是否⼤于6MB,如果⼩于6MB,则执⾏Full GC。
当新⽣代采⽤PSGC时,⽅式稍有不同,PS GC是在Minor GC后也会检查,例如上⾯的例⼦中第⼀次Minor GC后,PS GC会检查此时旧⽣代的剩余空间是否⼤于6MB,如⼩于,则触发对旧⽣代的回收。
除了以上4种状况外,对于使⽤RMI来进⾏RPC或管理的Sun JDK应⽤⽽⾔,默认情况下会⼀⼩时执⾏⼀次Full GC。可通过在启动时通过- java-
G1
什么是垃圾回收
⾸先,在了解G1之前,我们需要清楚的知道,垃圾回收是什么?简单的说垃圾回收就是回收内存中不再使⽤的对象。
垃圾回收的基本步骤
回收的步骤有2步:
1.查内存中不再使⽤的对象
2.释放这些对象占⽤的内存
1,查内存中不再使⽤的对象
那么问题来了,如何判断哪些对象不再被使⽤呢?我们也有2个⽅法:
1.引⽤计数法
引⽤计数法就是如果⼀个对象没有被任何引⽤指向,则可视之为垃圾。这种⽅法的缺点就是不能检测到环的存在。
2.根搜索算法
根搜索算法的基本思路就是通过⼀系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所⾛过的路径称为引⽤链(Reference Chain),当⼀个对象到GC Roots没有任何引⽤链相连时,则证明此对象是不可⽤的。
现在我们已经知道如何出垃圾对象了,如何把这些对象清理掉呢?
2. 释放这些对象占⽤的内存
常见的⽅式有复制或者直接清理,但是直接清理会存在内存碎⽚,于是就会产⽣了清理再压缩的⽅式。
总得来说就产⽣了三种类型的回收算法。
1.标记-复制
2.标记-清理
3.标记-整理
基于分代的假设
由于对象的存活时间有长有短,所以对于存活时间长的对象,减少被gc的次数可以避免不必要的开销。这样我们就把内存分成新⽣代和⽼年代,新⽣代存放刚创建的和存活时间⽐较短的对象,⽼年代存放存活时间⽐较长的对象。这样每次仅仅清理年轻代,⽼年代仅在必要时时再做清理可以极⼤的提⾼GC效率,节省GC时间。
Java垃圾收集器的历史
第⼀阶段,Serial(串⾏)收集器
在jdk1.3.1之前,java虚拟机仅仅能使⽤Serial收集器。 Serial收集器是⼀个单线程的收集器,但它的“单线程”的意义并不仅仅是说明它只会使⽤⼀个CPU或⼀条收集线程去完成垃圾收集⼯作,更重要的是在它进⾏垃圾收集时,必须暂停其他所有的⼯作线程,直到它收集结束。
PS:开启Serial收集器的⽅式
-XX:+UseSerialGC
第⼆阶段,Parallel(并⾏)收集器
Parallel收集器也称吞吐量收集器,相⽐Serial收集器,Parallel最主要的优势在于使⽤多线程去完成垃圾清理⼯作,这样可以充分利⽤多核的特性,⼤幅降低gc时间。
PS:开启Parallel收集器的⽅式
-XX:+UseParallelGC -XX:+UseParallelOldGC
第三阶段,CMS(并发)收集器
CMS收集器在Minor GC时会暂停所有的应⽤线程,并以多线程的⽅式进⾏垃圾回收。在Full GC时不再暂停应⽤线程,⽽是使⽤若⼲个后台线程定期的对⽼年代空间进⾏扫描,及时回收其中不再使⽤的对象。
PS:开启CMS收集器的⽅式
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
第四阶段,G1(并发)收集器
G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超⼤堆(⼤于4GB)时产⽣的停顿。相对于CMS的优势⽽⾔是内存碎⽚的产⽣率⼤⼤降低。
PS:开启G1收集器的⽅式
-XX:+UseG1GC
了解G1
G1的第⼀篇paper(附录1)发表于2004年,在2012年才在jdk1.7u4中可⽤。oracle官⽅计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS。为何oracle要极⼒推荐G1呢,G1有哪些优点
⾸先,G1的设计原则就是简单可⾏的性能调优
开发⼈员仅仅需要声明以下参数即可:
-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最⼤内存为32G,-XX:MaxGCPauseMillis=200设置GC的最⼤暂停时间为200ms。如果我们需要调优,在内存⼤⼩⼀定的情况下,我们只需要修改最⼤暂停时间即可。
其次,G1将新⽣代,⽼年代的物理空间划分取消了。
这样我们再也不⽤单独的空间对每个代进⾏设置了,不⽤担⼼每个代内存是否⾜够。
取⽽代之的是,G1算法将堆划分为若⼲个区域(Region),它仍然属于分代收集器。不过,这些区域的⼀部分包含新⽣代,新⽣代的垃圾收集依然采⽤暂停所有应⽤线程的⽅式,将存活对象拷贝到⽼年代或者Survivor空间。⽼年代也分成很多区域,G1收集器通过将对象从⼀个区域复制到另外⼀个区域,完成了清理⼯作。这就意味着,在正常的处理过程中,G1完成了堆的压缩(⾄少是部分堆的压缩),这样也就不会有cms内存碎⽚问题的存在了。
在G1中,还有⼀种特殊的区域,叫Humongous区域。 如果⼀个对象占⽤的空间超过了分区容量50%以上,G1收集器就认为这是⼀个巨型对象。这些巨型对象,默认直接会被分配在年⽼代,但是如果它是⼀个短期存在的巨型对象,就会对垃圾收集器造成负⾯影响。为了解决这个问题,G1划分了⼀个Humongous区,它⽤来专门存放巨型对象。如果⼀个H区装不下⼀个巨型对象,那么G1会寻连续的H分区来存储。为了能到连续的H区,有时候不得不启动Full GC。
PS:在java 8中,持久代也移动到了普通的堆内存空间中,改为元空间。
对象分配策略
说起⼤对象的分配,我们不得不谈谈对象的分配策略。它分为3个阶段:
1.TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区
2.Eden区中分配
3.Humongous区分配
TLAB为线程本地分配缓冲区,它的⽬的为了使对象尽可能快的分配出来。如果对象在⼀个共享的空间中分配,我们需要采⽤⼀些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每⼀个线程都有⼀个固定的分区⽤于分配对象,即⼀个TLAB。分配对象时,线程之间不再需要进⾏任何的同步。
对TLAB空间中⽆法分配的对象,JVM会尝试在Eden空间中进⾏分配。如果Eden空间⽆法容纳该对象,就只能在⽼年代中进⾏分配空间。
最后,G1提供了两种GC模式,Young GC和Mixed GC,两种都是Stop The World(STW)的。下⾯我们将分别介绍⼀下这2种模式。
G1 Young GC
Young GC主要是对Eden区进⾏GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年⽼代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到⽼年代空间中。最终Eden空间的数据为空,GC停⽌⼯作,应⽤线程继续执⾏。
这时,我们需要考虑⼀个问题,如果仅仅GC 新⽣代对象,我们如何到所有的根对象呢? ⽼年代的所有对象都是根么?那这样扫描下来会耗费⼤量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,作⽤是跟踪指向某个heap区内的对象引⽤。
在CMS中,也有RSet的概念,在⽼年代中有⼀块区域⽤来记录指向新⽣代的引⽤。这是⼀种point-out,在进⾏Young GC时,扫描根时,仅仅需要扫描这⼀块区域,⽽不需要扫描整个⽼年代。
但在G1中,并没有使⽤point-out,这是由于⼀个分区太⼩,分区数量太多,如果是⽤point-out的话,会造成⼤量的扫描浪费,有些根本不需要GC的分区引⽤也扫描了。于是G1中使⽤point-in来解决。point-in的意思是哪些分区引⽤了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了⽆效的扫描。由于新⽣代有多个,那么我们需要在新⽣代之间记录引⽤吗?这是不必要的,原因在于每次GC时,所有新⽣代都会被扫描,所以只需要记录⽼年代到新⽣代之间的引⽤即可。
需要注意的是,如果引⽤的对象很多,赋值器需要对每个引⽤做处理,赋值器开销会很⼤,为了解决赋值器开销这个问题,在G1 中⼜引⼊了另外⼀个概念,卡表(Card Table)。⼀个Card Table将⼀个分区在逻辑上划分为固定⼤⼩的连续区域,每个区域称之为卡。卡通常较⼩,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引⽤。当⼀个地址空间被引⽤时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引⽤,此外RSet也将这个数组下标记录下来。⼀般情况下,这个RSet其实是⼀个Hash Table,Key是别的Region的起始地址,Value是⼀个集合,⾥⾯的元素是Card Table的Index。
Young GC 阶段:
阶段1:根扫描
静态和本地对象被扫描
阶段2:更新RS
处理dirty card队列更新RS
阶段3:处理RS
检测从年轻代指向年⽼代的对象
阶段4:对象拷贝
拷贝存活的对象到survivor/old区域
阶段5:处理引⽤队列
软引⽤,弱引⽤,虚引⽤处理
G1 Mix GC
Mix GC不仅进⾏正常的新⽣代垃圾收集,同时也回收部分后台扫描线程标记的⽼年代分区。
它的GC步骤分2步:
1.全局并发标记(global concurrent marking)
2.拷贝存活对象(evacuation)
在进⾏Mix GC之前,会先进⾏global concurrent marking(全局并发标记)。 global concurrent marking的执⾏过程是怎样的呢?
在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是⼀次GC过程的⼀个必须环节。global concurrent marking的执⾏过程分为五个步骤:
初始标记(initial mark,STW)
在此阶段,G1 GC 对根进⾏标记。该阶段与常规的 (STW) 年轻代垃圾回收密切相关。
根区域扫描(root region scan
G1 GC 在初始标记的存活区扫描对⽼年代的引⽤,并标记被引⽤的对象。该阶段与应⽤程序(⾮ STW)同时运⾏,并且只有完成该阶段后,才能开始下⼀次 STW 年轻代垃圾回收。
并发标记(Concurrent Marking)
G1 GC 在整个堆中查可访问的(存活的)对象。该阶段与应⽤程序同时运⾏,可以被 STW 年轻代垃圾回收中断
最终标记(Remark,STW)
该阶段是 STW 回收,帮助完成标记周期。G1 GC 清空 SATB 缓冲区,跟踪未被访问的存活对象,并执⾏引⽤处理。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论