JVM垃圾回收机制和算法详解

时间:2022-12-26 21:20:40

1.判断对象已死吗?

两种算法:1)引用计数算法2)可达性分析算法

 

1) 引用计数算法:

实现思路:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1,;任何时刻计数器为0的对象就是不可能在被使用的。

缺点:很难解决对象之间相互循环引用的问题。(两个对象互为引用)

 JVM垃圾回收机制和算法详解

 JVM垃圾回收机制和算法详解

2) 可达性分析算法:(主流JVM使用的算法)

实现思路:通过一系列的称为GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连(图论叫懂GC Roots到这个对象不可达)时,则证明此对象是不可用的,即可回收的对象。

 

可作为GC Roots的对象包括下面几种:

1.虚拟机栈(栈帧中的本地变量表)中引用的对象。

2.方法区中类静态属性引用的对象。

3.方法区中常量引用的对象。

4.本地方法栈中JNI(即Native方法)引用的对象。

(总结:所有栈和方法区中会存在对象的引用,这些引用指向的对象都可被回收)


2.一个死亡的对象如何被回收?(java堆垃圾回收——针对对象)

  需要经历两次标记过程:

1)如果对象在进行可达性分析之后发现没有与GC roots 相连接的引用链,那会将它第一次标记,并判定是否有必要执行finalize()方法(当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,则称为没有必要执行finalize()方法)。

2)如果这个对象背判定为有必要执行finalize()方法,这个对象将被放入F-Queue队列,稍后GC将对队列中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己——需要重新与引用链上的任何一个对象建立关联即可。

 

注意:任何一个对下你给的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,也就逃不过死亡的命运了。(不建议使用finalize()方法!代价高昂!)

3.方法区垃圾回收(Java永久代)

1)废弃常量回收:某常量没有任何引用指向它了,如果有必要的话,这个常量就会被系统清理出常量池。常量池中的其他类(接口),方法,字段的符号引用也与此类似。

2)无用的类回收:

   判断无用的类需要满足三个条件:

   1.该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。

  2.加载该类的ClassLoader已经被回收。

  3.该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。

  (满则上述条件的类叫做可以被回收,而不是必然被回收,和对象回收不同!)


4.垃圾收集算法简介(4种)

1) 标记-清除算法(基础算法):

算法思想:先标记(什么对象需要被标记在上面已经讲了),再删除

缺点:1)效率不高2)空间问题:标记和清除会产生大量不连续的内存碎片。

2) 复制算法:

算法思想:将内存划分为大小相等的两块,当一块的内存用完了,就讲还存活的对象复 制到另外一块上面,然后将之前的那块清理掉。

(后HotSpot虚拟机将内存(新生代)分为8:1:1,一个Eden空间和两个survivor空间,每次使用一个Eden空间和一个survivor空间,因为新生代98%都是朝生夕死,当回收时,将这两块空间上的还存活的对象复制到空闲的另一个survivor空间上,然后清除这两个空间。如果偶尔存活对象超过10%,将触发分配担保机制,将放不下的存活对象放在老年代内存空间中)

缺点:内存缩小为原来的一半,浪费50%内存。不适合老年代垃圾回收。

3) 标记-整理算法:

    算法思想:将存活的对象都向一端移动,然后直接清理掉端边界以外的内存

4) 分代收集算法(目前主流算法):

算法思想:采用“分代收集”,即根据对象存活周期的不同将内存划分为几块。一般将java堆分为新生代和老年代。新生代中一般采用复制算法老年代中使用标记-清除”或“标记-整理”算法。

 

5.GC算法实现

1)枚举根节点:

(1)   GC停顿: 枚举根节点必须能在一个能确保一致性的快照中进行,这里的一致性指的是整个分析期间整个执行系统看起来就像被冻结在某一个时间点上,不可以出现分析过程中对象引用关系还在发生不断变化的情况,这一点不满足的话分析结果准确性就无法得到保证,这就是gc时必须停顿所有java执行线程的原因。

(2) 对象引用的获取:可作为GCRoots的节点主要在全局性的引用(如常量和类静态属性)与执行上下文(例如栈帧中的本地变量表)中,如果应用的方法区就有几百兆,那么如果逐个检查里面的引用,那么必然会消耗很多时间。  停顿下来以后,虚拟机并不需要一个不漏的检查所有执行上下文和全局变量引用,虚拟机应当有办法得知哪些地方存在着对象引用,HotSpot实现中有一个OopMap的数据结构,在类加载完成的时候,虚拟机就把对象内什么偏移量上是什么类型的数据计算出来,JIT编译过程中,也会特定的记录下栈和寄存器中哪些位置是引用,这样gc在扫描时就可以直接得知这些信息。

2)安全点:程序执行时只有到达安全点时餐能暂停开始GC。

   两种办法使程序运行到安全点就停下来:

(1) 抢先式中断(不采用):在GC发生时,首先把所有线程中断,如果发现有线程不在安全点上,就恢复线程,让它“跑”到安全点上。

(2)主动式中断:当GC需要中断线程的时候,不直接对线程操作,仅仅简单的设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的。

3)安全区域:指在一段代码片段之中,引用关系不会发生变化。即安全点的扩展,用来解决睡眠或锁的线程无法响应JVM中断请求,走到安全点去中断挂起的情况。