1. YGC,也称为 Minor GC
具体实现原理探索
复制算法是把内存划为3部分:Eden:Survivor1:Survivor2<br>它的产生来源是假设对象都是朝生夕死,用完就对。据统计一般我们的应用中这种情况非常多,所以我们Eden区用来创建对象,Survivor区用来存放过了Eden用了一次之后,还存在的对象。<br>YGC每次都检查Eden和一个Survivor,扫描之后。把Eden存活的还有扫描的Survivor存活的对象搬到另外个Survivor中,或者已经超过一定存活次数的,搬到老生代中。
从上面需要解决一些问题:<br>1. 在YGC中扫描GC Roots时怎么判断哪些GC Roots是YGC的,难道需要全部根都扫描一次?<br>2. 怎么知道存活的对象?
1. 在YGC中扫描GC Roots时怎么判断哪些GC Roots是YGC的,难道需要全部根都扫描一次?
针对这个问题,JVM设计了CSet这种概念。
CSet: Collection Set
CSet我理解会存放所有的GC Roots,并且GC Roots是有办法区分开的,比如有标志区分是年轻代/老生代。<br>如果性能高一点就会有不同的容器区分开年轻代/老生代。
YGC会选择对应的 CSet,即年轻代的 CSet
2. 如果有老生代的对象指向新生代的对象,改怎么处理?
跨代的处理,G1引入了RSet
RSet记录这对象跨HR之间的引用。这里的跨代,只有老年代指向年轻代,还有就是老年代指向老年代。
3. RSet怎么存放引用?RSet是全局吗?
1. RSet采用Point In的方式存放引用
1. 比如ObjA.field = ObjB. 那么在ObjB的RSet就会存放ObjA
这样的好处是扫描RSet只用扫描一层,扫描到ObjA就可以认为这是Root。否则就需要继续扫描到更深的地步。
2. RSet并不是全局的,RSet是每一HR一个。
4. 有了上面的扫描理论的基础,处理了根的引用还有跨HR之间的引用,差不多全了,那么我们可以猜测一下YGC的过程
1. STW
2. 扫描栈获取Eden的栈直接指向的对象。将对象从Eden 复制到Survivor
这里有个问题,我对象里面的field,这些引用怎么办?继续深层次的扫描再搬还是放入到队列之中第二次再搬?<br>G1采用的是第二种做法,将field放入一个queue之中(PSS队列)过后再来处理
3. 变更栈/老生代指向,指向到Suvivor。
4. 扫描RSet,同样可以认为根扫描到的对象是Root,然后复制到新的Survivor/老生代之中。
这里有个很大的问题?RSet什么时候写入的,这里去扫描,总得有地方写入吧。<br>上面的栈在对象分配的时候写入,这是毫无疑问的。<br>RSet的维护是一个大 工程,G1专门设计了一组线程Refine线程来管理RSet。Mutator线程也有可能直接更新RSet。
5. 变更栈/老生代对象的指向。
这里有个问题,为什么第3步和第5步没有合并?
没找到相关的资料,个人理解是代码复杂度的增加,都是性能没增加多少。<br>因为第4,5步有很多代码是复用第2,3步的代码
6. 扫描PSS队列,在PSS队列(G1ParScanThreadState)之中的对象都是活跃对象。每个对象直接复制到Survivor去。然后继续针对它的field,判断是否是在YGC的CSet中,在则把对象加入到PSS队列,待处理。<br>然后一直循环到PSS队列为空位置。