java:如何判断对象已死以及关于强引用、软引用、弱引用、虚引用

我们都知道,JVM会清理掉堆中已经“死"了的对象,那么如何判断对象已死呢?
判断对象已死有两种方法:

  • 引用计数法
  • 可达性分析算法

(1) 引用计数法:
核心思想:
引用计数法就是给每个对象加上一个引用计数器,每当有一个地方引用这个对象时,计数器的值就会+1,每当有一个引用失效时,引用器的值就会 -1,任意时刻,当某个对象计数器的值为0时,就认为这个对象不能再被使用了,即对象已死。
引用计数法实现简单,效率也高,python就是采用这种算法,但其实引用计数法没办法解决对象的循环引用问题。看下面的例子:

// 代码中count1,count2分别是test1,test2的引用计数器
public class Test {
     
    private Object instance;
    private static int _1MB = 1024 * 1024;
    private static byte[] bigsize = new byte[2 * _1MB];
    public static void main(String[] args) {
     
        Test test1 = new Test(); // count1 ++
        Test test2 = new Test(); // count2++

        test1.instance = test2;  // count2++
        test2.instance = test1;  // count1++

        test1 = null; // count1--
        test2 = null; // count2--

        System.gc();
    }
}

java:如何判断对象已死以及关于强引用、软引用、弱引用、虚引用_第1张图片
从上面的日志中我们可以看到, 6506K为垃圾回收前占用的内存,784K为回收后的,若test1,test2还存活,根据定义的数组,他两应该至少还需要4M的内存,故test1和test2应该已经死了。而如果按照引用计数法那样计算的话,此时test1和test2的计数器值都还为1,应该活着,但结果却显示它们已经死了,这是因为我们Java中并没有采用此算法判断对象是否死亡,而是采用可达性分析算法。
(2) 可达性分析算法
核心思想:
以一系列的"GCRoots"作为起点,从这些起点往下搜索对象,这些搜索过的路径称为"引用链",当一个对象与"GCRoots"间没有任何引用链相连时(从GCRoots到该对象不可达),我们就认为这个对象已经死了。
下图中,虽然object5~object7之间还彼此有联系,但它们与任何GCRoots之间都不相连了,因此JVM认为它们已经死了,即可以被回收了。
java:如何判断对象已死以及关于强引用、软引用、弱引用、虚引用_第2张图片
那么重点就是谁可以作为GCRoots,在java中我们有四种对象可以作为GCRoots:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类的静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中(本地方法)引用的对象

  上述描述,都提到了一个词"引用",在JDK1.2之前,Java中对引用的定义很传统,当一个引用类型内存放的数据代表的是另一块内存的起始地址,就称这块内存代表着一个引用。一个对象在这种定义下,只有引用和未被引用两种状态。
  我们希望能描述这样一类对象:当内存空间还够时,对象能保存在内存中,当内存在垃圾回收之后空间还是紧张,就可以把这些对象抛弃。
  因此,在JDK1.2之后,Java对引用的概念做了扩充,将引用分为四类:强引用、软引用、弱引用、虚引用,这四种引用的引用强度依此递减。

  • 强引用(Strong Reference):强引用指的是代码中普遍存在的对象,类似于 Object obj = new Object();
    也就是所有通过new出来的对象都是强引用,如obj就是个强引用。
    在JVM中只要强引用对象还存在,垃圾回收器永远不会回收该对象实例,即便内存不够用,也不会回收。
  • 软引用(Soft Reference):软引用对象描述一些有用但不必要的对象。仅被软引用指向的对象,在系统内存即将溢出之前,所有软引用对象会被垃圾回收。若内存够用,这些内存会保留。JDK1.2之后提供Soft Reference来实现软引用。
  • 弱引用(Weak Reference):弱引用比软引用更差一点,仅被弱引用关联的对象最多只能生存到下一次GC之前。当垃圾回收器开始工作时,无论内存是否够用,都会回收掉仅被弱引用关联的对象。JDK1.2之后使用Weak Reference来实现弱引用。
  • 虚引用(Phantom Reference):虚引用也称为幽灵引用或幻影引用,是最弱的一种引用关系。一个关系是否有虚引用的存在完全不影响它的生存时间,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是在这个对象被GC之前会收到一个系统通知。JDK1.2之后提供Phantom Reference来实现虚引用。

可以自己实现这四种引用:

public class Test {
     
    private Object instance;
    private static int _1MB = 1024*1024;
    private byte[] bigSize = new byte[2 * _1MB];
    public static void main(String[] args){
     
        Test test = new Test(); //test为强引用
        SoftReference softReference  = new SoftReference(test); //软引用 存活,内存大于2M故活着
         WeakReference softReference = new WeakReference(test);// 弱引用,第一次运行存活,二次	 									
        test = null;										//就死了
        System.gc(); //调用垃圾回收,把强引用置空,此时内存足够,软引用存活,将参数-Xmx调小,
    }			     // 但-Xmx要足够加载别的类信息,否则会OutOfMemory			
}

java:如何判断对象已死以及关于强引用、软引用、弱引用、虚引用_第3张图片
(3) 对象自我拯救
protected void finalize()throws Throwable{ }
  在可达性分析算法中不可达的对象也并非"非死不可",所有不可达的对象都处于一个"缓刑"阶段,要宣告一个对象的彻底死亡,还需要经历两次标记过程。
  若对象在可达性分析之后发现到GCRoots不可达,此对象会进行第一次标记并且进行一次筛选过程。筛选的条件是此对象是否有必要执行finalize(),当对象没有覆盖finalize()方法,或finalize()方法已经被JVM执行过了,JVM会将此对象彻底宣判死亡。
  若筛选成功,JVM会把此对象放入F-Queue(缓刑队列),如果对象在finalize()自救成功(成功与任意GCRoots建立连接),则对象会在第二次标记中被移除回收集合,成功存活。如果对象在finalize中仍然与GCRoots不可达,则宣告死亡。

你可能感兴趣的:(java:如何判断对象已死以及关于强引用、软引用、弱引用、虚引用)