强引用、软引用、弱引用、虚引用、终结器引用

2024-05-31  
2条评论   339浏览

基本概念

  1. 强引用(Strong Reference):
    • 定义:最常见的引用类型,通过普通变量名对对象的引用即为强引用。
    • 特性:只要强引用存在,垃圾回收器就不会回收被引用的对象。即使内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,也不会靠回收具有强引用的对象来解决内存不足的问题。
    • 示例:Object obj = new Object();
  2. 软引用(Soft Reference):
    • 定义:如果内存空间足够,垃圾回收器就不会回收被软引用指向的对象;如果内存空间不足,就会回收这些对象的内存。
    • 特性:可以用来实现内存敏感的高速缓存。
    • 示例:软引用常常和引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
  3. 弱引用(Weak Reference):
    • 定义:与软引用的主要区别在于,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否充足,都会回收它的内存。
    • 特性:弱引用同样可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
  4. 虚引用(Phantom Reference):
    • 定义:形同虚设,并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    • 特性:虚引用主要用来跟踪对象被垃圾回收器回收的活动。与软引用和弱引用的一个主要区别在于,虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
  5. 终结器引用(Finalizer Reference):
    • 定义:当对象变得不可达时,如果对象重写了finalize方法,它会被加入到一个由垃圾回收器维护的特殊队列中,这是通过终结器引用实现的。
    • 特性:主要用于在对象被垃圾回收前执行一些清理资源的操作。但需要注意的是,过度依赖finalize方法可能导致性能问题和资源泄漏,因为finalize方法的执行时机是不确定的,并且Java 9开始已经不建议使用finalize方法了。

基础使用

1. 强引用 (Strong Reference)

强引用是Java中最常见的引用类型。当一个对象具有强引用时,垃圾回收器(GC)永远不会回收它,即使系统出现内存不足的情况,也不会通过回收具有强引用的对象来解决内存问题。这意味着只要强引用存在,对象就会一直存在于内存中。

示例

Object strongRef = new Object(); // 创建一个强引用

2. 软引用 (Soft Reference)

软引用是用来描述一些还有用但并非必需的对象。当系统内存足够时,垃圾回收器不会回收软引用指向的对象;但是当系统内存不足时,垃圾回收器会回收这些对象的内存。软引用常常用于实现内存敏感的高速缓存。

示例

SoftReference<Object> softRef = new SoftReference<>(new Object()); // 创建一个软引用  
Object referent = softRef.get(); // 获取软引用指向的对象,如果对象已经被回收则返回null

3. 弱引用 (Weak Reference)

弱引用也是用来描述非必需对象的,但与软引用不同,弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾回收器工作时,无论当前内存是否足够,都会回收只被弱引用关联的对象。

示例

WeakReference<Object> weakRef = new WeakReference<>(new Object()); // 创建一个弱引用  
Object referent = weakRef.get(); // 获取弱引用指向的对象,如果对象已经被回收则返回null

4. 虚引用 (Phantom Reference)

虚引用是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用必须和引用队列(ReferenceQueue)联合使用。

示例

ReferenceQueue<Object> queue = new ReferenceQueue<>();  
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);  
// 注意:你不能通过phantomRef.get()来获取对象实例,因为总是返回null  
  
// 要检测对象是否被回收,需要轮询检查引用队列  
Reference<? extends Object> ref = queue.poll();  
if (ref != null) {  
    // 对象被回收了  
}

5. 终结器引用 (Finalizer Reference)

终结器引用是Java内部用来在对象被垃圾收集之前调用其finalize()方法的一个机制。但请注意,从Java 9开始,finalize()方法已被弃用,因为它可能会导致不可预测的行为和性能问题。现代的Java应用程序应该避免使用finalize()方法,并使用try-with-resources语句、AutoCloseable接口或其他清理机制来管理资源。

示例(不推荐使用):

@Deprecated  
public class MyClass {  
    @Override  
    protected void finalize() throws Throwable {  
        super.finalize();  
        // 清理资源的代码(不推荐使用)  
    }  
}

案例分析

package com.liyiru;

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

class MyObject
{
    //这个方法一般不用复写,我们只是为了教学给大家演示案例做说明
    @Override
    protected void finalize() throws Throwable
    {
        // finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作。
        System.out.println("-------invoke finalize method~!!!");
    }
}


public class ReferenceDemo
{
	//虚引用
    public static void main(String[] args)
    {
        MyObject myObject = new MyObject();
        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
        PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject,referenceQueue);
        //System.out.println(phantomReference.get());

        List<byte[]> list = new ArrayList<>();

        new Thread(() -> {
            while (true){
                list.add(new byte[1 * 1024 * 1024]);
                try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(phantomReference.get()+"\t"+"list add ok");
            }
        },"t1").start();

        new Thread(() -> {
            while (true){
                Reference<? extends MyObject> reference = referenceQueue.poll();
                if(reference != null){
                    System.out.println("-----有虚对象回收加入了队列");
                    break;
                }
            }
        },"t2").start();

    }


	//弱引用
    private static void weakReference()
    {
        WeakReference<MyObject> weakReference = new WeakReference<>(new MyObject());
        System.out.println("-----gc before 内存够用: "+weakReference.get());

        System.gc();
        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----gc after 内存够用: "+weakReference.get());
    }

	//软引用
    private static void softReference()
    {
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
        //System.out.println("-----softReference:"+softReference.get());

        System.gc();
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("-----gc after内存够用: "+softReference.get());

        try
        {
            byte[] bytes = new byte[20 * 1024 * 1024];//20MB对象
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            System.out.println("-----gc after内存不够: "+softReference.get());
        }
    }
	
	//强引用
    private static void strongReference()
    {
        MyObject myObject = new MyObject();
        System.out.println("gc before: "+myObject);

        myObject = null;
        System.gc();//人工开启GC,一般不用

        //暂停毫秒
        try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("gc after: "+myObject);
    }
}