Java中的引用类型: 弱引用、软引用与强引用详解

咦咦咦,各位小可爱,我是你们的好伙伴------bug菌,今天又来给大家普及Java SE相关知识点了,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~


🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!!

js 复制代码
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8

前言

在Java开发中,我们经常会遇到内存泄露、OOM等问题,其中与垃圾回收机制相关的问题也是我们需要注意的。Java中的引用类型分为强引用、软引用和弱引用,理解它们之间的区别和使用场景对于我们优化内存管理、提高程序性能都是非常有益的。本文将详细介绍Java中的三种引用类型的概念、源代码解析、优缺点分析以及应用场景案例,旨在帮助读者深入理解Java中引用类型的使用方法。

摘要

Java中引用类型包含强引用、软引用和弱引用三种类型,其中强引用是最常用的一种引用类型,其具有最高的优先级;软引用是指向一些有用但非必需的对象,可以在内存不足时回收;弱引用则是一种比软引用更加弱化的引用类型,常用于关联非必须对象的引用。本文将对这三种引用类型进行详细分析。

弱引用、软引用与强引用详解

简介

Java中引用类型是与内存回收机制相关的重要类型,它们的主要作用是帮助我们管理内存,在程序中对于不再被使用的内存进行回收。Java中引用类型主要包含强引用、软引用和弱引用三种类型。

强引用

强引用是默认的引用类型,在Java中使用最广泛。如果一个对象具有强引用,那么垃圾回收器就不会回收它,只有当内存不足时才会抛出OOM错误。

java 复制代码
MyObject obj = new MyObject();

上面的代码创建了一个MyObject对象,并将其赋值给一个强引用obj。只要obj存在,MyObject对象就不会被垃圾回收器回收。

软引用

软引用是指向一些有用但非必需的对象,可以在内存不足时回收。当内存不足时,垃圾回收器会回收被软引用指向的对象。软引用通常用于实现内存敏感的缓存等数据结构。

java 复制代码
SoftReference<MyObject>softRef = new SoftReference<MyObject>(new MyObject());

上面的代码创建了一个MyObject对象,并将其封装在一个软引用中。只有在内存不足时,MyObject对象才会被回收。

如下是部分源码截图:

弱引用

弱引用则是一种比软引用更加弱化的引用类型,常用于关联非必须对象的引用。如果一个对象仅持有弱引用,那么垃圾回收器在运行时,无论内存是否足够,都会回收这个对象。

java 复制代码
WeakReference<MyObject>weakRef = new WeakReference<MyObject>(new MyObject());

上面的代码创建了一个MyObject对象,并将其封装在一个弱引用中。无论内存是否足够,MyObject对象都可能会被垃圾回收器回收。

如下是部分源码截图:

源代码解析

强引用

强引用是Java中默认的引用类型,在Java程序中最为常用。它的原理非常简单,当一个对象具有一个强引用时,垃圾回收器就不会回收它。

java 复制代码
MyObject obj = new MyObject();

上面的代码创建了一个MyObject对象,并将其赋值给一个强引用obj。此时如果obj存在,MyObject对象就不会被垃圾回收器回收。

软引用

软引用是Java中的一种引用类型,用于指向那些有用但非必需的对象,可以在内存不足时回收。当内存不足时,垃圾回收器会回收被软引用指向的对象。

软引用常常用于实现内存敏感的缓存等数据结构。下面是一个简单的软引用的示例代码:

java 复制代码
SoftReference<MyObject>softRef = new SoftReference<MyObject>(new MyObject());

上面的代码创建了一个MyObject对象,并将其封装在一个软引用中。只有在内存不足时,MyObject对象才会被回收。

弱引用

弱引用则是一种比软引用更加弱化的引用类型,常用于关联非必须对象的引用。如果一个对象仅持有弱引用,那么垃圾回收器在运行时,无论内存是否足够,都会回收这个对象。下面是一个简单的弱引用的示例代码:

java 复制代码
WeakReference<MyObject>weakRef = new WeakReference<MyObject>(new MyObject());

上面的代码创建了一个MyObject对象,并将其封装在一个弱引用中。无论内存是否足够,MyObject对象都可能会被垃圾回收器回收。

应用场景案例

强引用

强引用是默认的引用类型,在Java程序中最为常用。强引用可以用于那些即便被回收也不会造成系统错误的对象,如程序的静态变量或者实例变量。下面是一个使用强引用的示例代码:

java 复制代码
public class Cache {
    private Map<String, MyObject>cache = new HashMap<>();

    public void put(String key, MyObject value) {
        cache.put(key, value);
    }

    public MyObject get(String key) {
        return cache.get(key);
    }
}

上面的代码创建了一个缓存Cache,使用Map存储键值对,其中MyObject对象使用强引用。

拓展:

这段代码定义了一个名为Cache的类,它包含一个私有的Map成员变量cache,用于存储字符串和MyObject对象的键值对。它提供了put和get方法,以将对象放入缓存中或从缓存中获取对象。

put方法使用传入的key和value参数将键值对存储在map中,其中key是一个字符串,value是一个类型为MyObject的对象。

get方法以key为参数,并返回存储在map中与该key关联的MyObject对象。如果map中不存在该key,则返回null。

此实现可用于简单的缓存系统,允许开发人员将对象存储在内存中,并在需要时快速检索它们。

软引用

软引用主要用于实现内存敏感的缓存等数据结构。下面是一个简单的缓存管理器示例代码,其中使用了软引用:

java 复制代码
public class CacheManager {
    private Map<String, SoftReference<MyObject>>cache = new HashMap<>();

    public void put(String key, MyObject value) {
        cache.put(key, new SoftReference<>(value));
    }

    public MyObject get(String key) {
        SoftReference<MyObject>softRef = cache.get(key);
        if (softRef != null) {
            return softRef.get();
        }
        return null;
    }
}

上面的代码创建了一个缓存管理器CacheManager,使用Map存储键值对。其中MyObject对象使用软引用,缓存管理器会在内存不足的时候自动清除这些对象。

拓展:

这是一个缓存管理器类,使用软引用来管理对象缓存,可以避免内存溢出的问题。

这缓存管理器通过一个 Map 来维护 key-value 对,并使用 SoftReference 来包装 MyObject 对象,使其成为软引用对象。put 方法将一个 key-value 对放入缓存中,使用 SoftReference 包装 value,get 方法则通过 key 获取对应的 SoftReference,并调用 SoftReference 的 get 方法获取 value 对象。如果获取不到 SoftReference 对象,则说明缓存中不存在该 key 对应的对象,返回 null。如果 SoftReference 存在但经过垃圾回收后 value 对象已被回收,则同样返回 null。

这由于软引用的特性,在内存不足且需要释放内存时,JVM 可以自动回收软引用所包装的对象,从而释放内存。这就避免了强引用所带来的内存泄漏问题,也避免了弱引用所带来的及时回收问题,使得软引用成为一种比较理想的内存管理方式。

弱引用

弱引用经常用于关联非必须对象的引用。下面是一个简单的代码示例,其中使用了弱引用:

java 复制代码
public class Cache {
    private Map<String, WeakReference<MyObject>>cache = new HashMap<>();

    public void put(String key, MyObject value) {
        cache.put(key, new WeakReference<>(value));
    }

    public MyObject get(String key) {
        WeakReference<MyObject>weakRef = cache.get(key);
        if (weakRef != null) {
            return weakRef.get();
        }
        return null;
    }
}

上面的代码创建了一个缓存Cache,使用Map存储键值对。其中MyObject对象使用弱引用,缓存管理器会在内存不足的时候自动清除这些对象。

拓展:

该代码实现了一个简单的缓存类,使用了HashMap和WeakReference。其中,HashMap用于存储键值对,而WeakReference用于对MyObject对象进行弱引用,避免在内存不足时引起OOM。

具体来说,当调用put方法时,将传入的key和MyObject对象封装在一个WeakReference中,再将其存入HashMap中。当需要获取某个key对应的对象时,先从HashMap中获取对应的WeakReference对象,再通过调用WeakReference的get方法获取MyObject对象。需要注意的是,由于WeakReference只是进行弱引用,当该对象没有强引用时,可能会被垃圾回收器回收,因此get方法的返回值可能为空。

此外,由于该代码没有对缓存进行任何清理,若缓存中的对象数量过多或者占用内存过大,可能会导致内存泄漏或OOM等问题。因此,建议在实际应用中对缓存进行定期清理或实现LRU策略等操作,以维持缓存的有效性和安全性。

优缺点分析

强引用

强引用是Java中默认的引用类型,使用最为广泛。它具有如下优点:

  • 强引用能够保证在任何时候都能够访问到其指向的对象,因此非常方便。

但也具有如下缺点:

  • 强引用可能导致内存泄露。如果一个对象具有强引用,即使它已经不再需要,垃圾回收器也不会回收它。这种情况下,如果这种对象占用的内存很大,就可能会导致内存泄露问题。

软引用

软引用是Java中的一种引用类型,用于指向那些有用但非必需的对象,可以在内存不足时回收。它具有如下优点:

  • 软引用可以有效地避免内存泄露问题。当内存不足时,垃圾回收器会回收被软引用指向的对象,从而释放内存。

但也具有如下缺点:

  • 软引用可能导致应用程序性能下降。当内存不足时,如果被软引用指向的对象很多,就会导致大量的垃圾回收操作,从而降低应用程序的性能。

弱引用

弱引用则是一种比软引用更加弱化的引用类型,常用于关联非必须对象的引用。它具有如下优点:

  • 弱引用可以有效地避免内存泄露问题。无论内存是否足够,垃圾回收器都可能会回收被弱引用指向的对象,从而释放内存。

但也具有如下缺点:

  • 弱引用有可能导致对象被过早地回收。由于弱引用指向的对象不再具有强引用,因此即使应用程序还需要这些对象,它们也有可能会被垃圾回收器回收。

测试用例

测试代码演示

Java中的引用类型分为强引用、软引用和弱引用。一般情况下,开发者使用的都是强引用来引用对象。当对象已经没有任何引用时,Java的垃圾回收器会自动回收这个没有引用的对象。但是如果我们需要在某些特殊场景中,对某些对象进行"缓存"或者"预处理",需要在对象没有引用时不要立即回收它们,而是希望它们能够在需要的时候再被回收,这个时候就需要使用Java的软引用和弱引用了。

下面是一个测试用例,演示Java中的强引用、软引用和弱引用的不同特点:

java 复制代码
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;

public class ReferenceTypeTest {

    public static void main(String[] args) throws InterruptedException {
        testStrongReference();
        testSoftReference();
        testWeakReference();
    }

    private static void testStrongReference() {
        Object obj = new Object();
        System.out.println("Strong reference: " + obj);
        obj = null;
        System.gc();
        System.out.println("After gc, Strong reference: " + obj);
    }

    private static void testSoftReference() {
        SoftReference<Object> obj = new SoftReference<>(new Object());
        System.out.println("Soft reference: " + obj.get());
        System.gc();
        System.out.println("After gc, Soft reference: " + obj.get());
    }

    private static void testWeakReference() throws InterruptedException {
        WeakReference<Object> obj = new WeakReference<>(new Object());
        System.out.println("Weak reference: " + obj.get());
        System.gc();
        System.out.println("After gc, Weak reference: " + obj.get());

        Thread.sleep(1000);
        System.out.println("After 1s, Weak reference: " + obj.get());
    }
}

输出结果:

java 复制代码
Strong reference: java.lang.Object@5acf9800
After gc, Strong reference: null
Soft reference: java.lang.Object@5acf9800
After gc, Soft reference: java.lang.Object@5acf9800
Weak reference: java.lang.Object@5acf9800
After gc, Weak reference: null
After 1s, Weak reference: null

可以看到:

  • 在强引用中,当对象没有引用时,垃圾回收器会立即回收这个对象。
  • 在软引用中,当内存空间足够时,对象不会被回收;当内存空间不足时,对象会被回收。
  • 在弱引用中,当垃圾回收器扫描到这个对象时,无论内存空间是否足够,都会将对象回收。在时间上,弱引用比软引用更容易回收。

测试结果

根据如上测试用例,本地测试结果如下,仅供参考,你们也可以自行修改测试用例或者添加更多的测试数据或测试方法,进行熟练学习以此加深理解。

测试代码分析

根据如上测试用例,在此我给大家进行深入详细的解读一下测试代码,以便于更多的同学能够理解并加深印象。

这是一个测试Java中三种引用类型(强引用、软引用、弱引用)的代码。其中:

  • testStrongReference()方法创建一个强引用对象,将其赋值给变量obj,之后将obj设置为null,然后调用System.gc()进行垃圾回收,最后输出obj的值。由于强引用是Java中默认的引用类型,当强引用被置为null后,该对象就会成为垃圾,被回收,因此输出为null。
  • testSoftReference()方法创建一个软引用对象,并使用get()方法获取其引用的对象,输出该对象,然后调用System.gc()进行垃圾回收,再次输出该对象。由于软引用类型的特性是:当内存不足时,JVM会回收软引用对象,故在调用System.gc()后,未必能清理掉软引用对象,输出的结果大概率是一样的。
  • testWeakReference()方法创建一个弱引用对象,使用get()方法输出该对象,然后调用System.gc()进行垃圾回收,再次输出该对象。由于弱引用类型的特性是:一旦一个弱引用对象被垃圾回收器确定为垃圾,被回收后,就会被立即清理,所以在调用System.gc()后,输出的结果应该为null。另外,为了让JVM有机会回收该对象,代码中还使用了Thread.sleep()进行等待,确保JVM有时间进行垃圾回收。

结论

Java中的引用类型包含强引用、软引用和弱引用三种类型。这三种引用类型在应用程序开发中都有其独特的优缺点,开发者需要根据不同的场景选择合适的引用类型。在合理地使用引用类型的同时,也需要注意避免内存泄露等问题的发生。

...   好啦,这期的内容就基本接近尾声啦,若你想学习更多,可以参考这篇专栏总结《「滚雪球学Java」教程导航帖》,本专栏致力打造最硬核 Java 零基础系列学习内容,🚀打造全网精品硬核专栏,带你直线超车;欢迎大家订阅持续学习。

附录源码

如上涉及所有源码均已上传同步在「Gitee」,提供给同学们一对一参考学习,辅助你更迅速的掌握。

☀️建议/推荐你


无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学Java」,bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门Java编程,就像滚雪球一样,越滚越大,指数级提升。

最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
  同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

📣关于我

我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。


相关推荐
一只叫煤球的猫4 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9655 小时前
tcp/ip 中的多路复用
后端
bobz9655 小时前
tls ingress 简单记录
后端
皮皮林5516 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友6 小时前
什么是OpenSSL
后端·安全·程序员
bobz9656 小时前
mcp 直接操作浏览器
后端
前端小张同学9 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook9 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康9 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在10 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net