G1垃圾回收
what:
G1全称Garbag first。早在JDK 7中就加入了。
其适合:大堆内存、小时延的回收。其解决了CMS中很多的缺陷。
核心思想:引入了分区的思想,弱化了分代的概念,从而合理利用垃圾回收各个周期的资源。
内存结构:
G1将heap划分为一系列大小相等的region,叫做“小堆区”。每个小堆区大小位1~32MB,默认分为2048个。与之前的分代垃圾回收方案类似,也份eden、survive和老年代,但是各自的region数并不固定,如下图:
G1中还有一个特殊的区域,就是Humongous。G1认为一个对象的大小超过region的50%以上,就认为是一个巨型对象,被放在Humongous中,如果一个H还是装不下,就是找连续的H来装,从而也会产生full GC。
注意:在 JDK8中,持久代已经移动到普通堆内存空间,改为元空间
对象分配方式:
对象分配分3个阶段,分别是:
a、TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区;
b、Eden区中分配;
c、Humongous区中分配;
在Eden中为每个线程分配了一块TLAB,这样可以快速分配空,不用在整个eden中做同步操作。
G1有2种GC模式:
young GC 和 Mixed GC
How:
Young GC:
young GC主要是对eden进行处理,在eden满时触发。执行情况如下:
注意:
做younng GC时,需要知道old区堆younng的引用情况。如果做整个old区扫描成本太大。所以CMS在老年代中引入了point-out的卡表,用来记录老年代对新生代的引用;而G1在新生代的region中使用了point-in的RSet,用来记录那些老年代引用了新生代的对象。
具体步骤:
a、根扫描:静态和本地对象扫描;
b、更新RS:处理dirty card的队列(写栅栏的writer buffer中的缓存),更新RS;
c、处理RS:老年代对新生代的引用;
d、对象拷贝:拷贝存活的对象到survivor/old区域
e、处理引用队列:软引用,弱引用,虚引用处理
Mixed GC:
它分2步,分别是:全局并发标记(global concurrent marking),和拷贝存活对象 (evacuation)。
“全局并发标记”只是为清理做标记服务,并不GC过程中必须环节。它分5步,分别是:
初始标记(STW):负责标记可以直接触达的根对象(原生栈对象、全局对象、JNI对象),根是对象图的起点。实际上,G1不是IHOP达到阈值后就马上开始,而是等到下一次young GC,利用young GC的STW时间进行标记,这就是借道(piggybacking)。在初始标记中,分区的nextTAMS赋值为top。初始标记是并发执行,直到所有的region处理完毕。
根区域扫描(root region scan):完成初始标记后,所有新生代存活的对象都移动到survivor区,应用线程开始活跃。为了保证标记算法的正确性,需要将survivor区的所有对象重新扫描一遍,并且标记为根。该survivor区就是Root Region。
并发标记(concurrent marking):和应用线程并发执行,并发标记线程在并发标记阶段启动,由参数-XX:ConcGCThreads(默认GC线程数的1/4,即-XX:ParallelGCThreads/4)控制启动数量,每个线程每次只扫描一个分区,从而标记出存活对象图。在这一阶段会处理Previous/Next标记位图,扫描标记对象的引用字段。同时,并发标记线程还会定期检查和处理STAB全局缓冲区列表的记录,更新对象引用信息。如果堆满前没有完成标记任务,则会触发担保机制,经历一次长时间的串行Full GC。
最终标记(remark STW):最后一个标记阶段,STW的找出SATB的日志缓冲区和所有更新,找到未访问的活的对象,并完成存活计数。这个阶段是并发的,通过XX:ParallelGCThread可设置GC的线程数。同时,引用处理也是重新标记阶段的一部分。
清除垃圾(cleanup STW): 清除(Clean)阶段也是STW的。同时Previous/Next标记位图、以及PTAMS/NTAMS,都会在清除阶段交换角色。