堆中存放着java中几乎所有的对象实例,垃圾收集器在堆堆进行回收前,首先要确定这些对象哪些还“活着”,哪些已经“死去”。有如下两种方法:
引用计数算法
为对象添加一个引用计数器,每当有一个地方引用该对象时,则该引用计数器值加1,;当引用失效时,则该引用计数器值减1;最后,计数器为0的对象就是不可能再被使用的,也即所谓的“死去”的对象。
Java虚拟机中并没有选用引用计算算法来管理内存,主要原因是很难解决对象间相互循环引用的问题。
public class TestReferenceCountingGC {
public Object instance = null;
public void testGc(){
TestReferenceCountingGC objA = new TestReferenceCountingGC();
TestReferenceCountingGC objB = new TestReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
在vm arguments中设置 -XX:+PrintGCDetails 的输出
[GC (System.gc()) [PSYoungGen: 2427K->600K(47104K)] 2427K->600K(155136K), 0.0150407 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 600K->0K(47104K)] [ParOldGen: 0K->523K(108032K)] 600K->523K(155136K), [Metaspace: 2570K->2570K(1056768K)], 0.0201145 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
Heap
PSYoungGen total 47104K, used 404K [0x000000078b780000, 0x000000078ec00000, 0x00000007c0000000)
eden space 40448K, 1% used [0x000000078b780000,0x000000078b7e5360,0x000000078df00000)
from space 6656K, 0% used [0x000000078df00000,0x000000078df00000,0x000000078e580000)
to space 6656K, 0% used [0x000000078e580000,0x000000078e580000,0x000000078ec00000)
ParOldGen total 108032K, used 523K [0x0000000722600000, 0x0000000728f80000, 0x000000078b780000)
object space 108032K, 0% used [0x0000000722600000,0x0000000722682e08,0x0000000728f80000)
Metaspace used 2577K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
GC日志中包含“2427K->600K”,意味着虚拟机并没有因为这两个对象相互引用就不回收他们,侧面说明虚拟机并不是通过引用计数算法来判断对象是否存活的。
可达性分析算法
算法基本思想:通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。
在java语言中,可作为GC Roots的对象包括下面几种:
- 虚拟机栈(帧栈中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
引用的分类
- 强引用(Strong Reference)
程序代码中普遍存在的,类似“Object obj = new Object()”,只要存在强引用,GC收集器永远不会回收被引用的对象。 - 软引用(Soft Reference)
非必须的对象,是否回收,要看当前内存情况,如果紧张,则进行回收,否则不回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。 - 弱引用(Weak Reference)
无论内存是否足够, 被弱引用关联的对象只能生存到下一次GC发生之前。即每次必被回收。 - 虚引用(Phantom Reference)
一个对象是否有虚引用的存在,不会影响到其生存时间,也无法通过虚引用获取对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收的时候收到一个系统通知。
生存还是死亡
即使在可达性分析算法中不可达的对象 ,也并非是“非死不可”的,要真正宣告一个对象死亡,至少要经历两次标记过程:
当没有发现引用链时,进行第一次标记并且进行一次筛选,筛选的条件是对象是否有必要执行finalize方法。当对象没有覆盖finalize方法,或者finalize方法已经被JVM调用过,此种情况下认为没有必要执行finalize方法。
如果这个对象有必要执行finalize方法,此时对象会被放置在一个F-Queue队列中,然后虚拟机会自动建立一个低优先级的Finalizer线程去触发finalize方法。finalize方法是对象逃脱死亡命运的最后一次机会。此时,GC会对队列中的对象进行第二次标记,如果对象在finalize方法中完成了自救,只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,则在第二次标记时该对象将被移出“即将回收”的集合。否则,只能判定对象死了。
一次对象自我拯救的演示
public class FinalizeEscapeGC {
public static FinalizeEscapeGC obj = null;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed");
FinalizeEscapeGC.obj = this;//与GC Roots的任何一个对象建立关联
}
public static void main(String[] args) throws Exception {
obj = new FinalizeEscapeGC();
//对象第一次成功拯救自己,对象覆盖了finalize()方法,判定为有必要执行finalize()方法,回收前成功逃脱
obj = null;
System.gc();
Thread.sleep(500);
if(obj != null){
System.out.println("i am still alive");
}else{
System.out.println("i am dead");
}
//自救失败,因为finalize()方法已经在上边调用过了,不会再调用finalize()方法了
obj = null;
System.gc();
Thread.sleep(500);
if(obj != null){
System.out.println("i am still alive");
}else{
System.out.println("i am dead");
}
}
}
运行结果
finalize method executed
i am still alive
i am dead