14-理解Java中的不同引用类型:强引用、软引用、弱引用和虚引用

简介: 这篇文章将深入探讨Java中的四种引用类型:强引用、软引用、弱引用和虚引用。我们将逐一解释每种引用类型的特性和用途。

Java有不同的引用类型,分别是:强引用、软引用、弱引用、虚引用,不同的引用类型跟我们的垃圾回收也有着不同的规则。

强引用

我们直接通过new关键字创建出来的对象都叫强引用对象,比如:

Object obj = new Object();

强引用的特点:

  1. 强引用可以直接访问目标对象。
  2. 强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
  3. 强引用可能导致内存泄漏。
软引用

软引用是除了强引用外,最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收(只有当JVM认为内存不足时,才会试图去回收软引用的对象,JVM 会确保在抛出OutOfMemoryError 之前,清理软引用指向的对象 )。因此,软引用可以用于实现对内存敏感的高速缓存

代码示例:

User user = new User();
SoftReference<User> softReference = new SoftReference<>(user);
user = null;//销毁强引用
System.gc();//手动垃圾回收
System.out.println(softReference.get());//打印软引用中user对象地址值:demo2.User@b4c966a

注意:触发软引用回收的点在于当内存空间已经装不下的时候以及内存空间很紧张的时候执行回收。

代码示例:

/**
 * @Description: 需要先配置参数 -Xms2M -Xmx3M,将 JVM 的初始内存设为2M,最大可用内存为 3M
 */
public class Test {
   
   
    private static List<Object> list = new ArrayList<>();
    public static void main(String[] args) {
   
   
        testSoftReference();
    }
    private static void testSoftReference() {
   
   
        for (int i = 0; i < 10; i++) {
   
   
            byte[] buff = new byte[1024 * 1024];
            SoftReference<byte[]> sr = new SoftReference<>(buff);
            list.add(sr);
        }

        System.gc(); //主动通知垃圾回收

        for(int i=0; i < list.size(); i++){
   
   
            Object obj = ((SoftReference) list.get(i)).get();
            System.out.println(obj);
        }

    }
}

打印结果:

null
null
null
null
null
null
null
null
null
[B@10f87f48
弱引用

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。

我们以同样的方式来测试弱引用:

private static void testWeakReference() {
   
   
        for (int i = 0; i < 10; i++) {
   
   
            byte[] buff = new byte[1024 * 1024];
            WeakReference<byte[]> sr = new WeakReference<>(buff);
            list.add(sr);
        }

        System.gc(); //主动通知垃圾回收

        for(int i=0; i < list.size(); i++){
   
   
            Object obj = ((WeakReference) list.get(i)).get();
            System.out.println(obj);
        }

    }

打印结果如下:

null
null
null
null
null
null
null
null
null
null

可以发现所有被弱引用关联的对象都被垃圾回收了。

虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。虚引用在创建时必须传入一个引用队列作为参数,当垃圾收集器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知应用程序对象的回收情况。

public class PhantomReference<T> extends Reference<T> {
   
   

    public T get() {
   
   
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
   
   
        super(referent, q);
    }
}

为一个对象设置虚引用关联的唯一目的在于跟踪垃圾回收过程。比如:能在这个对象被收集器回收时收到一个系统通知。

由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源释放操作放置在虚引用中执行和记录。

【生存还是死亡?】对象的finalization机制

现在理解了GC Roots和引用类型的概念后,也就知道了哪些对象可以被回收,哪些对象不能回收。

有GC Roots引用的对象不能回收,没有GC Roots引用的对象可以回收,如果有GC Roots引用,但是如果是软引用或者弱引用的,也有可能被回收掉。

问:假设没有GC Roots引用的对象,是一定立马被回收吗?

答:其实不是的,这里有一个 finalize()方法可以拯救他自己

示例代码:

/**
* 此代码演示了两点:
* 1.对象可以在被GC时自我拯救。
* 2.这种自救的机会只有一次, 因为一个对象的finalize()方法最多只会被系统自动调用一次
*/
public class TestFinalize {
   
   
    public static TestFinalize testFinalize;

    @Override
    protected void finalize() throws Throwable {
   
   
        super.finalize();
        System.out.println("当前类的finalize方法执行");
        testFinalize = this;
    }

    public static void main(String[] args) {
   
   
        testFinalize = new TestFinalize();
        //对象第一次成功拯救自己
        testFinalize = null;
        System.gc();
        System.out.println("第一次 gc......");
        try {
   
   
            //因为Finalizer方法优先级很低, 暂停0.5秒, 以等待它
            Thread.sleep(500);
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        }
        if(testFinalize == null){
   
   
            System.out.println("对象已死");
        }else{
   
   
            System.out.println("对象依然存活");
        }

 // 下面这段代码与上面的完全相同, 但是这次自救却失败了
        testFinalize = null;
        System.gc();
        System.out.println("第二次 gc......");
        try {
   
   
            Thread.sleep(500);
        } catch (InterruptedException e) {
   
   
            e.printStackTrace();
        }
        if(testFinalize == null){
   
   
            System.out.println("对象已死");
        }else{
   
   
            System.out.println("对象依然存活");
        }
    }
}

打印结果:

第一次 gc......
当前类的finalize方法执行
对象依然存活
第二次 gc......
对象已死

注意:finalize方法只会被调用一次!

永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用。理由包括下面三点:

  • 在 finalize()时可能会导致对象复活
  • finalize()方法的执行时间是没有保障的,它完全由Gc线程决定,极端情况下,若不发生GC,则 finalize()方法将没有执行机会
  • 一个糟糕的fnalize()会严重影响Gc的性能

从功能上来说, finalize()方法与C++中的析构函数比较相似,但是Java采用的是基于垃圾回收器的自动内存管理机制,所以 finalize()方法在本质上不同于c++中的析构函数。

其次它的运行代价高昂, 不确定性大, 无法保证各个对象的调用顺序, 如今已被官方明确声明为不推荐使用的语法。 有些教材中描述它适合做“关闭外部资源”之类的清理性工作, 这完全是对finalize()方法用途的一种自我安慰。 finalize()能做的所有工作, 使用try-finally或者其他方式都可以做得更好、更及时, 所以笔者建议大家完全可以忘掉Java语言里面的这个方法。

小结

到这儿已经把为什么要回收垃圾,以及哪些对象会被回收,哪些对象不会被回收的情况给大家介绍清楚了。接下来我们就要正式进入垃圾回收的算法以及内存分配策略的学习了。

目录
相关文章
|
4月前
|
缓存 Java 程序员
Java面试题:解释强引用、软引用、弱引用和虚引用在Java中是如何工作的?
Java面试题:解释强引用、软引用、弱引用和虚引用在Java中是如何工作的?
36 1
|
4月前
|
缓存 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,Java中的引用类型在内存管理和垃圾回收中的作用,Java中的finalize方法及其在垃圾回收中的作用,哪种策略能够提高垃圾回收的效率
Java面试题:深入探究Java内存模型与垃圾回收机制,Java中的引用类型在内存管理和垃圾回收中的作用,Java中的finalize方法及其在垃圾回收中的作用,哪种策略能够提高垃圾回收的效率
41 1
|
1月前
|
存储 Java 程序员
【一步一步了解Java系列】:何为数组,何为引用类型
【一步一步了解Java系列】:何为数组,何为引用类型
23 1
|
26天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
19 0
|
5月前
|
缓存 Java 数据库连接
java面试题目 强引用、软引用、弱引用、幻象引用有什么区别?具体使用场景是什么?
【6月更文挑战第28天】在 Java 中,理解和正确使用各种引用类型(强引用、软引用、弱引用、幻象引用)对有效的内存管理和垃圾回收至关重要。下面我们详细解读这些引用类型的区别及其具体使用场景。
80 3
|
4月前
|
Java 运维
开发与运维引用问题之软引用又在Java特点如何解决
开发与运维引用问题之软引用又在Java特点如何解决
42 0
|
5月前
|
缓存 Java 开发者
深入理解Java的五种引用类型
深入理解Java的五种引用类型
|
5天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
4天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
4天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
下一篇
无影云桌面