1、判断对象是否存活的算法:
<1>、引用计数法:(每个对象实例中都有一个引用计数器)当一个对象被创建的时候,该实例对象分配给一个变量,此时计数器为1。之后当其他变量引用该对象实例时,计数器+1,而当引用的变量超过生命周期,或改变为引用其他对象实例时,该对象实例计数器-1。当计数器为0的时候,则说明该对象实例没有被引用,即可以被垃圾回收掉。
缺点:当2个对象之间相互引用时,则这2个对象实例无法被回收掉,因为他们之间相互引用。
关于缺点的理解,伪代码说明:
{
O1 o1 = new O1();
O2 o2 = new O2();
o1.instance = o2;//实例O1中的一个成员变量是实例O2类型
o2.instance = o1;//实例O2中的一个成员变量是实例O1类型
o1 = null;//虽然o1 = null ,但是O2中的一个成员变量引用了O1的实例,即引用变量由 o1—>O2.instance
o2 = null;同理如上
}
<2>、可达性分析:由定义好的GC Roots节点作为起始点开始,将所有有联系的节点全部连接到一起,最终形成类似树状的结构,此时,不在树上的节点即为可回收对象
a、优点:解决了互相引用,无法回收的问题
b、GC Roots节点对象有如下几种:
1、方法区常量引用的对象
2、方法区中静态成员变量引用的对象
3、虚拟机栈中引用的对象【栈桢中的本地变量表】
4、本地方法栈中本地方法引用的对象
c、二次标记机制:在可达性分析中,当被确定为可回收对象时,进行第一次标记。若该对象没有重写 finalize(),或者在重写时,没有为自己与GC Roots 树建立连接,此时进行第二次标记。二次标记结束后,当垃圾回收的时候,就会被回收掉。
注意:二次标记机制,finalize()中建立与GC Roots 树连接,一个对象只有一次机会,不会通过在finalize()中建立与GC Roots 树连接,出现一直无法回收的现象。
2、方法区垃圾回收
方法区中被回收的对象有2种:1、废弃常量;2、无用的类
<1>、废弃常量通过可达性分析 来判断
<2>、无用的类通过以下几点来判断是否回收:
1、该类所有的实例都被回收
2、加载该类的ClassLoader 被回收
3、该类对应的Java.lang.Class 对象没有任何地方引用,同时无法通过反射来获取该类的方法等等。
3、垃圾回收的算法
<1>、标记清除算法
采用从 GC Roots 点开始扫描,对存活的对象进行标记,标记结束之后,再扫描整个空间中未被标记的对象进行回收。
缺点:产生内存碎片
<2>、复制回收算法 --- 解决句柄的开销和内存碎片的问题
将空间分为2部分,同样从GC Roots节点开始扫描,将存活的对象复制到另外一半中,全部扫描结束后,清空原来一半空间。
优点:不会产生内存碎片,高效
缺点:占用内存空间大
<3>、标记整理算法
标记所有存活的对象,扫描并清除整个空间中未标记的对象,在回收死亡的对象时,将存活的对象往左边空闲空间移动,建立在标记清除算法之上
优点:不会产生内存碎片
缺点:开销大
<4>、分代收集算法
目前大部分JVM使用的垃圾回收算法,核心思想是根据对象生命周期划分为若干个不同区域:新生代、老年代。新生代由于每次回收时都有大量的对象需要被回收、老年代的特点是每次垃圾回收时,只有少量对象被回收。因此,可以根据不同的区域使用不同的垃圾回收算法。
年轻代回收算法-复制回收算法(分区比例是8:1:1);老年代中回收的对象少,适合标记整理 和标记清除算法
过程:
将年轻代分为 Eden区、s0(From)区、s1(To)区。新对象产生一般在Eden区,如果Eden区满了,则将Eden区中存活的对象复制到s0区-箭头1,清空Eden区【young GC】;如果s0区满了,则将Eden区和s0区中存活的对象复制到s1区-箭头2,清空Eden区 和 s0区【young GC】,同时将s0于s1 互换一下 -箭头2.5,确保s1区是空的。同时如果其中的对象达到了阈值(在s区中经历默认为15次young GC之后),就将达到阈值的对象放入老年代中。
如果s1区不足以存放Eden区和s0区中存活的对象则 将存活的对象直接复制到老年代 - 箭头3。如果老年代也满了,则会触发一次Full GC,频率低
如果Eden区不足以存放新建的对象,则会触发担保机制,将新建对象直接存储到老年代中。