JVM
中所有的引用类型,都是抽象类java.lang.ref.Reference
的子类,这个类的主要方法为get()
方法:
public abstract class Reference<T> {}
除了虚引用(因为get
永远返回null
),如果对象还没有被销毁,都可以通过get
方法获取原有对象。这意味着,利用软引用和弱引用,我们可以将访问到的对象,重新指向强引用,也就是人为的改变了对象的可达性状态。
public T get() { return this.referent; }
强引用:
强引用(Strong References
)就是直接new
一个普通对象,表示一种比较强的引用关系,只要还有强引用对象指向一个对象,那么表示这个对象还活着,垃圾收集器宁可抛出OOM
异常,也不会回收这个对象。
public class StrongReferenceDemo { public static void main(String[] args) throws IOException { // 创建一个User对象,此时u就是一个强引用: User u = new User(); // 由于强引用,u引用不会被回收: System.gc(); System.out.println(u); // u这个引用指向为空,这时new出来,在堆空间的User实例,没有引用指向 // 在进行GC时,这个实例就会被GC回收 u = null; // 手动出发GC,这时User实例就会被GC回收,并触发finalize方法: System.gc(); System.in.read(); } } public class User { // 重写finalize方法,在触发GC时调用(这个方法调用时,该对象将被进行回收) @Override protected void finalize() throws Throwable { System.out.println("call User finalize() method"); } }
上面的User
对象重写了父类Object
的finalize()
,在GC
准备释放对象所占用的内存空间之前,它将首先调用finalize()
方法。
在Java
中,由于GC
的自动回收机制,因而并不能保证finalize
方法会被及时地执行(垃圾对象的回收时机具有不确定性),也不能保证它们会被执行(程序由始至终都未触发垃圾回收),所以finalize
不推荐使用,这里只是为了演示垃圾回收的过程。
另外finalize()
最多只会被调用一次,也就是只能利用finalize()
为对象续命一次。
弱引用:
软引用用于存储一些可有可无的东西,例如缓存,当系统内存充足时,这些对象不会被回收,当系统内存不足时也是GC
时才会回收这些对象,如果回收完这些对象后内存还是不足,就会抛出OOM
异常。
// vm args: -Xmx36m -XX:+PrintGCDetails public class SoftReferenceDemo { public static void main(String[] args) throws InterruptedException { SoftReference<User> softReference = new SoftReference<>(new User()); // 软引用 System.out.println(softReference.get()); System.gc(); // wait gc thread run TimeUnit.SECONDS.sleep(3); // 由于堆内存空间充足,这时候软引用不会被GC回收 // User对象不会被回收 System.out.println(softReference.get()); // 分配一个大对象使得堆空间不足,软引用对象会在OOM之前先被回收 byte[] bytes = new byte[1024 * 1024 * 1024]; System.out.println(softReference.get()); } }
在上面的例子中,第一次发生gc
时,User
对象不会被回收,第二次发生gc
时由于堆空间不足,会先回收软引用的对象,回收完了还是空间不足,最后抛出OOM
异常。
软引用:
弱引用(WeakReference
)并不能使对象豁免垃圾回收,仅仅是提供一种访问在弱引用状态下对象的途径。只要发生gc
,弱引用对象就会被回收。ThreadLocal
中就使用了WeakReference
来避免内存泄漏。
public class WeakReferenceDemo { public static void main(String[] args) throws InterruptedException { WeakReference<User> weakReference = new WeakReference<>(new User()); System.out.println(weakReference.get()); System.gc(); TimeUnit.SECONDS.sleep(3); // wait gc thread run System.out.println(weakReference.get()); // null } }
上面的例子只要发生gc
,User
对象就会被垃圾收集器回收。
虚引用:
虚引用必须和引用队列(ReferenceQueue
)联合使用。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象之前,把这个虚引用加入到与之关联的引用队列中。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
// vm args: -Xms4m -XX:+PrintGC public class PhantomReferenceDemo { public static void main(String[] args) throws IOException, InterruptedException { // 引用队列 ReferenceQueue<User> referenceQueue = new ReferenceQueue<>(); // 虚引用 PhantomReference<User> phantomReference = new PhantomReference<>(new User(), referenceQueue); // 虚引用在调用get时返回值一定为null: System.out.println(phantomReference.get()); // null // 创建一个子线程: new Thread(() -> { while (true) { Reference<? extends User> poll = referenceQueue.poll(); if (poll != null) { System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll); System.out.println("--- 回收对象 ---- " + poll.get()); // null } } }).start(); TimeUnit.SECONDS.sleep(1); System.gc(); System.in.read(); } private static class User { private int[] bytes = new int[1024 * 1024 * 5]; @Override protected void finalize() throws Throwable { System.out.println("call User finalize() method"); } } }
基于虚引用,有一个更加优雅的实现方式,那就是Cleaner
,可以用来替代Object
类的finalizer
方法,在DirectByteBuffer
中用来回收堆外内存。