Java强引用、软引用、弱引用及虚引用深入探讨

强引用、软引用、弱引用和虚引用深入探讨

为了更灵活的控制对象的生命周期,在JDK1.2之后,引用被划分为强引用、软引用、弱引用、虚引用四种类型,每种类型有不同的生命周期,它们不同的地方就在于垃圾回收器对待它们会使用不同的处理方式。

引用类型在日常开发中并不常关注,也很少注意到,因此很多人忽略了它们的存在,而事实上,引用类型在Java体系中扮演着十分重要的角色,要想对Java体系有一个更深层次的理解,了解和掌握这些引用的用法是十分必要的。

在正式开始前,我们先来上两道开胃菜。

为什么需要回收

每一个Java程序中的对象都会占用一定的计算机资源,最常见的,如:每个对象都会在堆空间上申请一定的内存空间。但是除了内存之外,对象还会占用其它资源,如文件句柄,端口,socket等等。当你创建一个对象的时候,必须保证它在销毁的时候会释放它占用的资源。否则程序将会在OOM中结束它的使命。

在Java中不需要程序员来管理内存的分配和释放,Java有自动进行内存管理的神器——垃圾回收器,垃圾回收器会自动回收那些不再使用的对象。

在Java中,不必像C或者C++那样显式去释放内存,不需要了解其中回收的细节,也不需要担心会将同一个对象释放两次而导致内存损坏。所有这些,垃圾回收器都自动帮你处理好了。你只需要保证那些不再被使用的对象的所有引用都已经被释放掉了,否则,你的程序就会像在C++中那样结束在内存泄漏中。

虽然垃圾回收器确实让Java中的内存管理比C、C++中的内存管理容易许多,但是你不能对于内存完全不关心。如果你不清楚JVM到底会在什么条件下才会对对象进行回收,那么就有可能会不小心在代码中留下内存泄漏的bug。

因此,关注对象的回收时机,理解JVM中垃圾收集的机制,可以提高对于这个问题的敏感度,也能在发生内存泄漏问题时更快的定位问题所在。想了解更多关于垃圾回收相关的细节,可以参考这篇文章

为什么需要引用类型

引用类型是与JVM密切合作的类型,有些引用类型甚至允许在其引用对象在程序中仍需要的时候被JVM释放。

那么,为什么需要这些引用类型呢?

在Java中,垃圾回收器线程一直在默默的努力工作着,但你却无法在代码中对其进行控制。无法要求垃圾回收器在精确的时间点对某些对象进行回收。

有了这些引用类型之后,可以一定程度上增加对垃圾回收的粒度把控,可以让垃圾回收器在更合适的时机回收掉那些可以被回收掉的对象,而并不仅仅是只回收不再使用的对象。

这些引用类型各有特点,各有各的适用场景,清楚的了解和掌握它们的用法可以帮助你写出更加健壮的代码。

说明

在JDK 1.2以前的版本中,如果一个对象没有被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有当对象处于可达(reachable)状态时,程序才能使用它。只有在对象没有任何其他对象引用它时,垃圾回收器才会对它进行收集。对象只有被引用和没有被引用两种状态。这种方式无法描述一些“食之无味,弃之可惜”的对象。

而很多时候,我们希望存在这样一些对象:当内存空间足够时,可以将它们保存在内存中,不进行回收;当内存空间变得紧张时,允许JVM回收这些对象。大部分缓存都符合这样的场景。

从JDK 1.2版本开始,Java对引用的概念进行了扩充,对象的引用分成了4种级别,从而使程序开发者能更加灵活地控制对象的生命周期,更好的控制创建的对象何时被释放和回收。

这4种级别由高到低依次为:强引用软引用弱引用虚引用

实力翻车

欢迎来到大型翻车现场,接下来将实力演示一波因为强引用过多导致的翻车例子。

如果你需要在整个程序运行期间保存一些对象(因为它们的初始化很耗费时间和资源),你可能会使用静态集合对象来存储并且在代码中随处使用它们。

public static Map<K, V> storedObjs = new HashMap<>();

但是这样,就会阻止垃圾回收器对集合中的对象进行回收和销毁。从而可能导致OOM的发生。例如:

public class OOMTest {
    public static List<Integer> cachedObjs = new ArrayList<>();
 
    public static void main(String[] args) {
        for (int i = 0; i < 100_000_000; i++) {
            cachedObjs.add(i);
        }
    }
}

输出如下:

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

这样就符合预期的翻车了。但你也许会说,谁会这么无聊,创建这么多变量。

嗯,确实是的,但是别忘了,一个程序可能会运行很长时间,几个月,甚至几年(如果你的代码和公司足够健壮的话),如果期间不断的创建变量而不清理的话(像上面那样把HashMap当缓存使用),是有可能会导致这种情况发生的。

内容编排

接下来的文章将从以下几方面对这四种引用进行介绍:

注意 本系列文章都是以JDK1.8 版本的代码进行分析,不同版本中代码会略有差异。

如果只是想要对这些引用进行简单了解,那么看完简要介绍部分即可,如果想要有更深入的研究,可以继续查阅源码剖析部分。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AndroidTv

谈谈你对 Java 平台的理解声明提问正文

1114
来自专栏.NET技术

GC的前世与今生

  虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久。早在1958年,由鼎鼎大名的图林奖得主John McCarthy所实现的Lisp语言就...

1503
来自专栏Crossin的编程教室

【Python 第23课】 if, elif, else

今天补充之前讲过的一个语句:if。为什么我跳要着讲,因为我的想法是先讲下最最基本的概念,让你能用起来,之后你熟悉了,再说些细节。 关于if,可以发送数字『7』回...

2826
来自专栏HTML5学堂

面向对象系列讲解—面向对象的含义&工厂模式

HTML5学堂:在上一篇文章当中,我们把对象进行了基本的解释,本文当中,我将为大家解释什么是面向对象?为何要使用面向对象,而不用面向过程,面向对象又有什么好处,...

2876
来自专栏Python小屋

Python语言编程规范与优化建议

没有规矩,不成方圆。代码任何一种语言都有一些约定俗成的编码规范,Python也不例外。Python非常重视代码的可读性,对代码布局和排版有更加严格的要求。虽然一...

3174
来自专栏闰土大叔

闰土说JS进阶之作用域链

前言 在前端应聘中,相信会有不少面试官都会问你,说说你理解的js作用域,或者作用域链。显然,这是一道经典的js面试题,对于老司机而言可谓是小菜一碟,而对于前端...

3369
来自专栏每周一脱topic

Effictive python学习总结连载(1)

python从读研开始就在用了,拿来做过web后台、安全分析、爬虫、测试框架等等,挺强大的。最近借放假和看书和整理的机会,系统的总结下。主要是2方面:一个是书或...

2102
来自专栏Web 开发

JavaScript Cookbook 2nd 之 Function

昨晚翻了一下,虽然都是一些旧知识,不过深入下去对照着其他资料一起看,还是能发现一些有意思的地方。

880
来自专栏醒者呆

基础大扫荡——背包,栈,队列,链表一口气全弄懂

提到数据结构,不得不说数据类型,有人将他们比作分子和原子的关系,我们都知道大自然最小的构成单位是原子,数据类型描述的是原子的内部,如质子、中子的情况,而数据结构...

41215
来自专栏算法与数据结构

利用代码计算原码,反码和补码

1063

扫码关注云+社区

领取腾讯云代金券