JVM面试篇
JVM如何解决跨代问题
如果在年轻代的某个对象引用了老年代的对象,在触发Young GC的时候只会检查年轻代而不会检查老年代,此时如果老年代引用了一个年轻代的对象就不会被YGC发现而回收了此对象。但是这种情况出现的比较少,除非是老年代动态的引用了一个刚产生的对象。
记忆集和卡表
如果为了小概率事件每次YGC都去扫描一遍永久代,开销会很大。此时我们只需要保存从非扫描区域到扫描区域的指针就可以了。这个保存的扫描指针逻辑上叫做记忆集,放在新生代中。
卡表是hotspot虚拟机对于记忆集的具体实现,类似位图,把非扫描区域分块,然后用一个卡表数组来记录对应块是否变脏,如果记录的为1说明变脏,在YGC的时候把对应块加入扫描。本质上是用空间换时间。
写屏障
这里的写屏障跟volatile的不一样,这里仅仅是对引用操作做了一层aop,在写后屏障加入添加卡表的操作,在写前屏障添加该页是否脏的判断,可以解决伪共享问题。
三色标记算法
三色标记算法是可达性分析的一个扫描过程,例如CMS,先做初始标记标记出GCroot,然后再并发标记,这个过程是通过三色标记算法实现的。最后扫描到的白色节点就被视为没有引用,在下一轮GC会被回收。
- 黑色,直接或者间接连接到GCroot并且自身所有引用都被扫描过了
- 灰色:从黑色节点扩散,但是自己的子节点还没有被完全扫描
- 白色:没有被染指的节点,也就是不可达区域
三色标记的缺点
- 多标问题:在一个节点被标记成灰色之后,上一个节点断开联系,最终这个节点还是会被染黑,但实际上是垃圾。这个现象称为浮动垃圾,在下一轮会被回收。
- 漏标问题:多标至少可以通过下一轮垃圾回收清除,漏标就严重多了。当一个灰色节点与一个白色节点断掉之后立刻有黑色节点相连。这个时候三色标记算法还是会认为这个节点是白色的,就会被垃圾回收,而此时确实有黑色节点的引用,会报空指针异常。

如何解决
两个条件同时发生才会有漏标现象:首先是有灰色节点与白色节点断开联系,又有黑色节点与白色节点产生联系。
破坏一个就不会产生漏标:
- 增量更新:增量更新破坏了第一个条件:「至少有一个黑色对象新增了对白色对象的引用」,在并发标记阶段,黑色对象D指向了白色对象G,这时会把黑色对象D记录下来,在重新标记阶段,会把黑色对象D标记为灰色对象D,然后以灰色对象D为根节点,扫描整个引用链,白色对象G就会被依次标记为灰色、黑色,白色对象G漏标的问题就解决了。 缺点是会重新扫描以黑色对象为根节点的子树,时间长(CMS解决方案)
- 原始快照:原始快照破坏了第二个条件:「所有灰色对象指向该白色对象的引用都断开了」,在并发标记阶段,灰色对象E断开了对白色对象G的引用,这是会把白色对象G记录下来,在最终标记阶段,会把白色对象G标记为灰色,然后以灰色对象G为根节点,扫描整个引用链,如此以来原来的白色对象G就会被依次标记为灰色、黑色,白色对象G漏标的问题就解决了。缺点是如果没有黑色对象来引用就会变成浮动垃圾(G1解决方案)
类的回收
类的元数据在方法区也就是元空间中,在fullGC是有可能会被垃圾回收的。一个类能被垃圾回收需要满足三个要求:
- 没有实例对象
- 没有静态方法的引用
- 类加载器已经被GC
private static final int I = 0,这个i会回收吗
看情况,跟着类走,本身这就是一个GCroot,一共有四种GCroot(类的常量引用类似这种,静态引用,虚拟机栈的引用,本地方法栈的引用)。如果不是类被卸载的话是不能被GC的。