在堆中存放着Java世界中几乎所有的对象实例,垃圾收集器在回收前,第一件事情是判断这些对象哪些是已经死去,可以回收的。判断的方式主要有两种:引用计数法和可读性分析。
一、引用计数算法
原理:给对象添加一个引用计数器,当有地方引用它时,计算器就加1,当引用失效时,计数器减1。任何计数器为0的对象,被判断为不可能再被使用。
优点:实现简单,判断效率很高,在大部分情况下都是一个不错的算法。例如微软的COM、ActionScript3的FlashPlayer、Python语言以及在游戏领域被广泛应用的Squirrel都使用了引用计数算法进行内存管理。
缺点:难以解决对象之间互相循环引用问题。也因此,在主流Java虚拟机中,都没有使用该算法来进行内存管理。
二、可达性分析算法
原理:这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(也就是GC Roots到这个对象不可达),则证明此对象是不可用的。主流的商用程序语言(Java、C#、古老的Lisp),都使用该算法来判断对象是否存活的。
![](https://img.haomeiwen.com/i18742409/ed22d2359de7ccbc.png)
如下代码:
public class ReferenceGCTest {
public static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
testGC();
}
public static void testGC() {
TestE object5 = new TestE();
TestF object6 = new TestF();
object5.object6 = object6;
object6.object5 = object5;
System.out.println("对象存活情况1:");
System.out.println(object5);
System.out.println(object6);
object5 = null;
object6 = null;
TestA object1 = new ReferenceGCTest.TestA();
object1.object2 = new ReferenceGCTest.TestB();
object1.object3 = new ReferenceGCTest.TestC();
object1.object3.object4 = new ReferenceGCTest.TestD();
try {
Thread.sleep(1*1000);
}catch(Exception e) {
}
System.out.println("对象存活情况2:");
System.out.println(object1);
System.out.println(object1.object2);
System.out.println(object1.object3);
System.out.println(object1.object3.object4);
System.out.println(object5);
System.out.println(object6);
System.out.println(object1.object3.object4.bigSize.length);
}
//object1
public static class TestA{
public TestB object2 = null;
public TestC object3 = null;
public String toString() {
return "object1 alive";
}
}
//object2
public static class TestB{
private byte[] bigSize = new byte[10 * _1MB];
public String toString() {
return "object2 alive";
}
}
//object3
public static class TestC{
public TestD object4 =null;
public String toString() {
return "object3 alive";
}
}
//object4
public static class TestD{
public byte[] bigSize = new byte[10 * _1MB];
public String toString() {
return "object4 alive";
}
}
//object5
public static class TestE{
public TestF object6 = null;
private byte[] bigSize = new byte[20 * _1MB];
public String toString() {
return "object5 alive";
}
}
//object6
public static class TestF{
public TestE object5 = null;
private byte[] bigSize = new byte[20 * _1MB];
public String toString() {
return "object6 alive";
}
}
}
如果按照引用计数法,object5和object6因为相互引用而无法被回收,实际上java虚拟机使用的可达性分析算法,两个对象是可以被回收的,可以在启动程序时加上内存参数(不能太大造成不会进行gc操作,也不能太小造成OOM异常),同时加上打印gc信息参数,查看详细信息:
-Xms100m -Xmx100m -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:./gclogs
程序运行结果:
对象存活情况1:
object5 alive
object6 alive
对象存活情况2:
object1 alive
object2 alive
object3 alive
object4 alive
null
null
10485760
![](https://img.haomeiwen.com/i18742409/fe21974b3186c4f8.png)
Java中可作为GC Roots对象的有以下几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
三、引用的几种类型
无论是通过引用计数算法还是可达性分析算法,判断对象的存活都与“引用”有关。在JDK1.2以前,对象只有被引用和没有被引用两种状态,JDK1.2之后,对引用概念进行了扩充,引用分为强引用、软引用、弱引用、虚引用4种,这4种引用的前度依次减弱。这样,当内存空间还足够时,有些对象可以作为缓存保留在内存中,进过垃圾收集后,如果仍然不够,则可以抛弃这些对象。
强引用:在程序代码中普遍存在的,类似Object.obj = new Object这类的引用,只要强引用还存在,垃圾收集器永远都不会回收掉被引用的对象。
软引用(SoftReference):是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。
弱引用(WeekReference):也是用来描述非必须对象的。但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。WeakHashMap和ThreadLocal用到了弱引用。ThreadLocal线程本地变量,如果线程本身都已经消亡,那ThreadLocal中map保留的与对应线程相关的本地变量,自然也就没有用。
虚引用(PhantomReference):也称幽灵引用或幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过需引用来取得一个对象实例。jdk中直接内存的回收就用到虚引用。
以上几种引用的使用场景可以参考文章:
https://www.jianshu.com/p/825cca41d962
四、finalize()方法
java程序员,最好忘记有该方法的存在。finalize()方法只会被系统调用一次。
网友评论