在 JVM 的垃圾回收策略中,无论是基于 “引用计数” 算法判断对象的引用数量,还是基于 “可达性分析” 算法判断对象是否引用链可达,在判断对象是否应该被回收时都离不开对象的 “引用”。
在 JDK1.2 之前,Java 中的对象只存在 “被引用” 和 “未被引用” 两种状态。
Java 中对引用的定义:如果 refence 类型的数据中存储的数值代表的是另一块内存的起始地址,就称该 refence 数据是代表某块内存、某个对象的引用。
但随着程序的空间复杂度越来越高,如何更有效地利用内存就成了大家思考的重点。为了使程序能更加灵活地控制对象的生命周期, Java 在 JDK1.2 后对 “引用” 的概念进行了更细粒度的划分,由高到低依次为:“强引用” 、"软引用" 、“弱引用” 、“虚引用” 、“未引用”。
- JDK: - java version "1.8.0_202" - Java(TM) SE Runtime Environment (build 1.8.0_202-b08) - Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode) - OS:Windows 10 - IDE: - IntelliJ IDEA 2021.1.3 (Ultimate Edition)
只创建了对象,但没有对该对象进行任何引用。
// 此处的代码只是创建了一个 Object 类型的对象, // 但是这个对象没有被引用。 new Object();
使用一个变量名存储了对象在内存中的地址(引用赋值),此时对象就处于强引用状态。
OutOfMemoryError
错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。// 此处的代码在创建了一个Object类型的对象后, // 将这个对象在内存中的地址赋值给了变量o, // 如此该对象就处于强引用状态 Object o = new Object(); // 主动开启JVM的GC System.gc(); // 注意:实际上 JVM 何时进行 GC 操作并不可控! // 我们调用System.gc()方法只是起通知作用,JVM 什么时候扫描回收对象是 JVM 自己根据当前系统的状态决定的。 // 因此更严谨的做法是休眠等待一段时间再验证 GC 的情况 Thread.sleep(5000); // 此时的变量o依然可以引用到之前的对象。 System.out.println(o);
Object
仍保存在 Java 堆中)。当这个方法执行完后,就会被弹出栈,若此时该强引用对象的引用数为 0,那么这个对象就会被回收。elementData[i] = null
// ArrayList 的 clear() 方法 /** * Removes all of the elements from this list. The list will * be empty after this call returns. */ public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
但是也有资料认为这种显式的赋 null 值没有意义。在 JIT 编译下,JIT 编译器进行控制流和数据流分析后,生成的 OopMap 能提供比较精确的信息,不需要通过 null 值来告知对象使命已经完成。退一步说,这时即使有 "=null" 操作,也会被优化掉,生成出来的本地代码与没有 "=null" 操作的版本是一样的。
使用 SoftReference
类对某个对象进行 “描述包装” ,标志对象处于软引用状态。
if( JVM 内存不足 ) { // 将软引用中的对象引用置为 null str = null; // 通知垃圾回收器进行回收 System.gc(); }
// 此处代码先创建了一个Object类型的对象, // 然后使用SoftReference对这个新建对象进行 “描述包装”, // 如此这个新建对象就只处于软引用状态 SoftReference<Object> soft = new SoftReference<>(new Object()); // 处于软引用状态的对象,可以使用SoftReference对象的get方法获取 // 如果不想给对象添加其他引用状态,那么只可以使用SoftReference对象的get方法获取 // SoftReference对象的get方法的返回值 就好比 强引用状态中的变量名 // 强引用状态中的变量名如何使用,那么SoftReference对象的get方法的返回值就如何使用 System.out.println(soft.get()); // 主动开启JVM的GC System.gc(); // 注意:实际上 JVM 何时进行 GC 操作并不可控! // 我们调用System.gc()方法只是起通知作用,JVM 什么时候扫描回收对象是 JVM 自己根据当前系统的状态决定的。 // 因此更严谨的做法是休眠等待一段时间再验证 GC 的情况 Thread.sleep(5000); // 因为此时系统内存依然充足,因此 JVM 并不会主动的去释放处于软引用的对象 System.out.println(soft.get());
用来描述非必要的对象,且强度比软引用更弱一些。 使用 WeakReference
类对某个对象进行 “描述包装” ,标志对象处于弱引用状态。
WeakReference
来记住此对象。// 此处代码先创建了一个Object类型的对象, // 然后使用WeakReference对这个新建对象进行 “描述包装”, // 如此这个新建对象就只处于弱引用状态 WeakReference<Object> weak = new WeakReference<>(new Object()); // 处于弱引用状态的对象,可以使用WeakReference对象的get方法获取 // 如果不想给对象添加其他引用状态,那么只可以使用WeakReference对象的get方法获取 // WeakReference对象的get方法的返回值 就好比 强引用状态中的变量名 // 强引用状态中的变量名如何使用,那么WeakReference对象的get方法的返回值就如何使用 System.out.println(weak.get()); // 主动开启JVM的GC System.gc(); // 注意:实际上 JVM 何时进行 GC 操作并不可控! // 我们调用System.gc()方法只是起通知作用,JVM 什么时候扫描回收对象是 JVM 自己根据当前系统的状态决定的。 // 因此更严谨的做法是休眠等待一段时间再验证 GC 的情况 Thread.sleep(5000); // 输出为null // 因为GC将只处于弱引用的对象给回收掉了, // 所以WeakReference对象无法再引用到之前的 “描述包装” 的对象了 System.out.println(weak.get());
使用 PhantomReference
类对某个对象进行 “描述包装” ,标志对象处于虚引用状态。 虚引用状态完全不会对对象的生存时间构成影响,也无法通过 PhantomReference
对象来取得一个对象实例。
别称:幽灵引用、幻影引用。
虚引用必须和引用队列
ReferenceQueue
联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), new ReferenceQueue<Object>()); // 输出null System.out.println(phantom.get());
在创建 SoftReference
、WeakReference
、PhantomReference
对象时,如果使用两参数的构造函数,而构造函数的参数二是ReferenceQueue
对象时,那么当对象被 JVM 的 GC 回收时,JVM 会将被回收了对象的 SoftReference
、WeakReference
、PhantomReference
对象添加进 ReferenceQueue
对象,我们可以通过 ReferenceQueue
对象的 remove()
方法获取到添加进ReferenceQueue
对象的SoftReference
、WeakReference
、PhantomReference
对象。
冰,水为之,而寒于水。
|