全国做网站的公司,手机网站建设专业服务公司,网站识别手机电脑自动跳转,旅游网络网站建设方案三色标记、增量更新、原始快照、记忆集与卡表 三色标记基本原来错标、漏标错标漏标 增量更新基本原理写屏障 原始快照基本原理为什么G1使用原始快照而不用增量更新。 记忆集与卡表 三色标记
基本原来
三色标记是JVM的垃圾收集器用于标记对象是否存活的一种方法。
三色是指黑… 三色标记、增量更新、原始快照、记忆集与卡表 三色标记基本原来错标、漏标错标漏标 增量更新基本原理写屏障 原始快照基本原理为什么G1使用原始快照而不用增量更新。 记忆集与卡表 三色标记
基本原来
三色标记是JVM的垃圾收集器用于标记对象是否存活的一种方法。
三色是指黑、白、灰三种颜色用这三种颜色记录不同对象的状态。
黑被标记为黑色的对象表示该对象已经被GC线程扫描过并且它所有引用的对象都被扫描过。白白色对象是所有对象一开始的颜色表示该对象还未被GC线程扫描到。灰被标记为灰色的对象表示该对象已经被GC线程扫描过但是它还有引用的对象没有被扫描到。
GC垃圾搜集线程一旦完成遍历堆内存中要么就是黑色的对象要么就是白色的对象。如果是黑色的对象表示它可以通过GC Roots的引用链访问到如果是白色的对象表示无法通过GC Roots的引用链访问到。 那么此时GC垃圾收集线程就可以把白色对象视作垃圾对象把它们清理掉释放它们占用的内存空间。 这样看起来一切正常但这是建立在GC进行垃圾收集时用户线程停顿的前提下也就是有STW的前提下。如果是并发标记也就是用户线程在GC进行对象图遍历标记存活对象的时候用户线程继续运行就会出现错标和漏标的问题。
错标、漏标
错标
错标就是多标也就是这个对象是个垃圾对象但是GC把它标记为存活对象。
这种现象一般是由于GC垃圾收集线程已经把一个对象遍历到并标记为灰色或者黑色然后用户线程把到该对象的引用链断开了。
一个对象被标记为黑色或者灰色那么该对象就会被认定为存活对象。然后用户线程才把到该对象的引用链断开此时GC垃圾收集线程是不知道的GC垃圾收集线程对于已经遍历过的对象是不会再遍历的因此该对象就会被当成存活对象被保留下来。但由于引用链已经断开无法再访问到该对象因此实际上该对象是个垃圾对象是应该被回收的因此这种现象就叫错标或者多标也就是这个标记是多余的。 这种问题好解决只要到下一次垃圾收集还是会作为白色对象被回收的。
漏标
漏标是指本该被标记为存活的对象但是GC没有标记导致该对象被回收此时用户线程访问该对象就会发生空指针异常。
这种现象的发生必须满足两个条件
一个灰色对象断开了对一个白色对象的引用。一个黑色对象又建立对这个白色对象的应用。 在GC完成剩下的对象的遍历之后GC就会把它作为白色对象把它回收掉。 如果用户线程通过这个空指针引用去访问这个被GC回收掉的对象就会发生空指针异常这显然是一个很严重的问题。
解决这个问题的方法就是增量更新和原始快照。
增量更新
基本原理
增量更新是CMS用于解决漏标的处理方式。也就是当黑色对象建立对白色对象的引用时把该黑色对象标记为灰色。 然后在并发标记阶段结束之后在重新标记阶段从新从该灰色对象开始进行深度优先遍历。 这样GC就可以把该白色对象标记为黑色就不会被GC当成垃圾对象被回收。
但是把黑色对象修改为灰色的这个操作如何实现的呢这肯定不是由GC垃圾收集线程完成的因为它是不会在并发标记阶段重新扫描已经扫描过的对象的。
写屏障
实际上这个操作是通过写屏障实现写屏障就是在指针赋值操作前后插入一段额外的逻辑。 增量更新是在黑色对象建立到白对象引用的之后把该黑色对象标记为黑色的因此自然是把写屏障插入到指针赋值操作的后面。 然后在重新标记阶段GC就可以从该灰色对象开始重新进行扫描。 原始快照
基本原理
G1垃圾收集器解决漏标的办法则是原始快照原始快照是在灰色对象断开对白色对象的引用时把被删除的灰色对象到白色对象的引用记录下来把白色对象修改为灰色。 这样GC就可在最终标记阶段通过该快照引用继续扫描到该白色对象。 由于是在灰色对象断开对白色对象的引用前把该引用作为快照保存下来因此是在指针赋值操作前插入的写屏障。 为什么G1使用原始快照而不用增量更新。
我个人觉得G1之所有使用的是原始快照而不是增量更新是因为原始快照比起增量更新来说在最终标记阶段需要扫描的对象更少。
我们根据上面的例子看看增量更新需要重新扫描的对象
然后再看看使用原始快照需要扫描多少个对象
可以看到使用增量更新需要扫描三个对象。而使用原始快照只需要重新扫描1个对象。
这个例子只有少量几个对象如果在真实场景下对象的数量会非常非常多这样性能差距就很明显了。并且G1它是面向大内存的垃圾收集器使用原始快照就会比起使用增量更新来说少扫描很多的对象。
记忆集与卡表
JVM的垃圾收集分为年轻代与老年代两个区域年轻代的对象朝生夕死老年代的对象存活的时间较长。因此JVM对年轻代的垃圾收集频率肯定是比老年代的垃圾收集频率要高的。
但是在JVM中存在跨代引用的现象也就是老年代的对象引用了年轻代的对象或者是年轻代的对象引用了老年代的对象。但是JVM在进行年轻代的垃圾收集时只会对年轻代进行扫描不会扫描老年代那么如果一个年轻代对象仅存在老年代对象对它的引用时GC是扫描不到它的自然就不会被标记为存活对象。 那么该对象就会被当成垃圾对象回收当我们通过老年代对象指向该年轻代对象的引用访问该年轻代对象时就会发生空指针异常。 因此GC在进行年轻代的垃圾收集时不能只扫描年轻代的对象还要扫描老年代中存在跨代引用的对象。如果直接把整个老年代都扫描的话年轻代GC的性能就太低了因此JVM定义了一个记忆集Remember Set记录非收集区到收集区的引用。比如现在要对年轻代进行垃圾收集那么此时年轻代区域就是收集区由于此时不需要对老年代进行垃圾收集所以老年代就是非收集区。 记忆集只是一个抽象的概念不同的Java虚拟机对记忆集有不同的实现比如Hotspot虚拟机对记忆集的实现就是卡表。
首先Hotspot把内存划分为一小块一小块的区域这些区域叫做卡页然后定义一个字节数组CART_TABLE[]这个字节数组就是卡表卡表中的每一个byte对应内存中的一个卡页。 如果老年代中的一个对象引用了年轻代中的一个对象那么该老年代对象所在的卡页对应在卡表中的那一个byte就会被修改为1表示该卡页脏了。 那么当JVM要进行年轻代的垃圾收集时通过CART_TABLE即可得知老年代区域中哪些卡页是脏页就会扫描老年代中的脏页把脏页中的对象加入到GC Roots中。 这样被老年代对象引用的年轻代对象也可以被扫描到就不会被当成垃圾对象被回收了。
这个修改卡表对应byte为脏的动作也是通过写屏障触发的。