理解Java的强引用、软引用、弱引用和虚引用

栏目: Java · 发布时间: 6年前

内容简介:引用计数:Java堆中每一个对象都有一个引用计数属性,引用每新增1次计数加1,引用每释放1次计数减1。在

Java 执行 GC 判断对象是否 存活 有两种方式其中一种是 引用计数

引用计数:Java堆中每一个对象都有一个引用计数属性,引用每新增1次计数加1,引用每释放1次计数减1。

JDK 1.2 以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于( reachable ) 可达状态 ,程序才能使用它。

JDK 1.2 版本开始,对象的引用被划分为 4 种级别,从而使程序能更加灵活地控制 对象的生命周期 。这 4 种级别 由高到低 依次为: 强引用软引用弱引用虚引用

理解 <a href='https://www.codercto.com/topics/22013.html'>Java</a> 的强引用、软引用、弱引用和虚引用

正文

1. 强引用(StrongReference)

强引用是使用最普遍的引用。如果一个对象具有强引用,那 垃圾回收器 绝不会回收它。如下:

Object strongReference = new Object();
复制代码

内存空间不足 时, Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序 异常终止 ,也不会靠随意 回收 具有 强引用对象 来解决内存不足的问题。 如果强引用对象 不使用时 ,需要弱化从而使 GC 能够回收,如下:

strongReference = null;
复制代码

显式地设置 strongReference 对象为 null ,或让其 超出 对象的 生命周期 范围,则 gc 认为该对象 不存在引用 ,这时就可以回收这个对象。具体什么时候收集这要取决于 GC 算法。

public void test() {
        Object strongReference = new Object();
        // 省略其他操作
    }
复制代码

在一个 方法的内部 有一个 强引用 ,这个引用保存在 Java 中,而真正的引用内容( Object )保存在 Java 中。 当这个 方法运行完成 后,就会退出 方法栈 ,则引用对象的 引用数0 ,这个对象会被回收。

但是如果这个 strongReference全局变量 时,就需要在不用这个对象时赋值为 null ,因为 强引用 不会被垃圾回收。

ArrayList的Clear方法:

理解Java的强引用、软引用、弱引用和虚引用

ArrayList 类中定义了一个 elementData 数组,在调用 clear 方法清空数组时,每个数组元素被赋值为 null 。 不同于 elementData=null ,强引用仍然存在,避免在后续调用 add() 等方法添加元素时进行内存的 重新分配 。 使用如 clear() 方法 内存数组 中存放的 引用类型 进行 内存释放 特别适用,这样就可以及时释放内存。

2. 软引用(SoftReference)

如果一个对象只具有 软引用 ,则 内存空间充足 时, 垃圾回收器不会 回收它;如果 内存空间不足 了,就会 回收 这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

软引用可用来实现内存敏感的高速缓存。

// 强引用
    String strongReference = new String("abc");
    // 软引用
    String str = new String("abc");
    SoftReference<String> softReference = new SoftReference<String>(str);
复制代码

软引用可以和一个 引用队列 ( ReferenceQueue )联合使用。如果 软引用 所引用对象被 垃圾回收JAVA 虚拟机就会把这个 软引用 加入到与之关联的 引用队列 中。

ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
    String str = new String("abc");
    SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);

    str = null;
    // Notify GC
    System.gc();

    System.out.println(softReference.get()); // abc

    Reference<? extends String> reference = referenceQueue.poll();
    System.out.println(reference); //null
复制代码

注意:软引用对象是在jvm内存不够的时候才会被回收,我们调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM自己的状态决定的。就算扫描到软引用对象也不一定会回收它,只有内存不够的时候才会回收。

当内存不足时, JVM 首先将 软引用 中的 对象 引用置为 null ,然后通知 垃圾回收器 进行回收:

if(JVM内存不足) {
        // 将软引用中的对象引用置为null
        str = null;
        // 通知垃圾回收器进行回收
        System.gc();
    }
复制代码

也就是说, 垃圾收集线程 会在虚拟机抛出 OutOfMemoryError 之前回 收软引用对象 ,而且 虚拟机 会尽可能优先回收 长时间闲置不用软引用对象 。对那些 刚构建 的或刚使用过的**"较新的" 软对象会被虚拟机尽可能 保留**,这就是引入 引用队列 ReferenceQueue 的原因。

应用场景:

浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

  1. 如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建;
  2. 如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出。

这时候就可以使用软引用,很好的解决了实际的问题:

// 获取浏览器对象进行浏览
    Browser browser = new Browser();
    // 从后台程序加载浏览页面
    BrowserPage page = browser.getPage();
    // 将浏览完毕的页面置为软引用
    SoftReference softReference = new SoftReference(page);

    // 回退或者再次浏览此页面时
    if(softReference.get() != null) {
        // 内存充足,还没有被回收器回收,直接获取缓存
        page = softReference.get();
    } else {
        // 内存不足,软引用的对象已经回收
        page = browser.getPage();
        // 重新构建软引用
        softReference = new SoftReference(page);
    }
复制代码

3. 弱引用(WeakReference)

弱引用与 软引用 的区别在于:只具有 弱引用 的对象拥有 更短暂生命周期 。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有 弱引用 的对象,不管当前 内存空间足够与否 ,都会 回收 它的内存。不过,由于垃圾回收器是一个 优先级很低的线程 ,因此 不一定很快 发现那些只具有 弱引用 的对象。

String str = new String("abc");
    WeakReference<String> weakReference = new WeakReference<>(str);
    str = null;
复制代码

JVM 首先将 软引用 中的 对象 引用置为 null ,然后通知 垃圾回收器 进行回收:

str = null;
    System.gc();
复制代码

注意:如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。

下面的代码会让一个 弱引用 再次变为一个 强引用

String str = new String("abc");
    WeakReference<String> weakReference = new WeakReference<>(str);
    // 弱引用转强引用
    String strongReference = weakReference.get();
复制代码

同样, 弱引用 可以和一个 引用队列 ( ReferenceQueue )联合使用,如果 弱引用 所引用的 对象垃圾回收Java 虚拟机就会把这个 弱引用 加入到与之关联的 引用队列 中。

简单测试:

GCTarget.java

public class GCTarget {
    // 对象的ID
    public String id;

    // 占用内存空间
    byte[] buffer = new byte[1024];

    public GCTarget(String id) {
        this.id = id;
    }

    protected void finalize() throws Throwable {
        // 执行垃圾回收时打印显示对象ID
        System.out.println("Finalizing GCTarget, id is : " + id);
    }
}
复制代码

GCTargetWeakReference.java

public class GCTargetWeakReference extends WeakReference<GCTarget> {
    // 弱引用的ID
    public String id;

    public GCTargetWeakReference(GCTarget gcTarget,
              ReferenceQueue<? super GCTarget> queue) {
        super(gcTarget, queue);
        this.id = gcTarget.id;
    }

    protected void finalize() {
        System.out.println("Finalizing GCTargetWeakReference " + id);
    }
}
复制代码

WeakReferenceTest.java

public class WeakReferenceTest {
    // 弱引用队列
    private final static ReferenceQueue<GCTarget> REFERENCE_QUEUE = new ReferenceQueue<>();

    public static void main(String[] args) {
        LinkedList<GCTargetWeakReference> gcTargetList = new LinkedList<>();

        // 创建弱引用的对象,依次加入链表中
        for (int i = 0; i < 5; i++) {
            GCTarget gcTarget = new GCTarget(String.valueOf(i));
            GCTargetWeakReference weakReference = new GCTargetWeakReference(gcTarget,
                REFERENCE_QUEUE);
            gcTargetList.add(weakReference);

            System.out.println("Just created GCTargetWeakReference obj: " +
                gcTargetList.getLast());
        }

        // 通知GC进行垃圾回收
        System.gc();

        try {
            // 休息几分钟,等待上面的垃圾回收线程运行完成
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 检查关联的引用队列是否为空
        Reference<? extends GCTarget> reference;
        while((reference = REFERENCE_QUEUE.poll()) != null) {
            if(reference instanceof GCTargetWeakReference) {
                System.out.println("In queue, id is: " +
                    ((GCTargetWeakReference) (reference)).id);
            }
        }
    }
}
复制代码

运行 WeakReferenceTest.java ,运行结果如下:

理解Java的强引用、软引用、弱引用和虚引用

可见 WeakReference 对象的生命周期基本由 垃圾回收器 决定,一旦垃圾回收线程发现了 弱引用对象 ,在下一次 GC 过程中就会对其进行回收。

4. 虚引用(PhantomReference)

虚引用顾名思义,就是 形同虚设 。与其他几种引用都不同, 虚引用不会 决定对象的 生命周期 。如果一个对象 仅持有虚引用 ,那么它就和 没有任何引用 一样,在任何时候都可能被垃圾回收器回收。

应用场景:

虚引用主要用来 跟踪对象 被垃圾回收器 回收 的活动。 虚引用软引用弱引用 的一个区别在于:

虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

String str = new String("abc");
    ReferenceQueue queue = new ReferenceQueue();
    // 创建虚引用,要求必须与一个引用队列关联
    PhantomReference pr = new PhantomReference(str, queue);
复制代码

程序可以通过判断引用 队列 中是否已经加入了 虚引用 ,来了解被引用的对象是否将要进行 垃圾回收 。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的 内存被回收之前 采取必要的行动。

总结

Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

垃圾回收器 回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从 根对象 Object标记 存活的对象,然后将某些不可达的对象和一些引用的对象进行回收。

通过表格来说明一下,如下:

引用类型 被垃圾回收时间 用途 生存时间
强引用 从来不会 对象的一般状态 JVM停止运行时终止
软引用 当内存不足时 对象缓存 内存不足时终止
弱引用 正常垃圾回收时 对象缓存 垃圾回收后终止
虚引用 正常垃圾回收时 跟踪对象的垃圾回收 垃圾回收后终止

欢迎关注技术公众号: 零壹技术栈

理解Java的强引用、软引用、弱引用和虚引用

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Beginning PHP and MySQL 5

Beginning PHP and MySQL 5

W. Jason Gilmore / Apress / 2006-01-23 / USD 44.99

Beginning PHP and MySQL 5: From Novice to Professional, Second Edition offers comprehensive information about two of the most prominent open source technologies on the planet: the PHP scripting langua......一起来看看 《Beginning PHP and MySQL 5》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具