Java中的四种(强、软、弱和虚)引用

一、强引用(StrongReference)

强引用很常见,其写法如下:

StringBuffer buffer = new StringBuffer();

上面创建了一个 StringBuffer 对象,并将这个对象的(强)引用存到变量 buffer 中。强引用最重要的就是它能够让引用变得强(Strong),这就决定了它和垃圾回收器的交互。具体来说,如果一个对象通过一串强引用链接可到达(Strongly reachable),它是不会被回收的。如果不想让正在使用的对象被回收,这正是所需要的。

但是强引用如此之强,在一个程序里,将一个类设置成不可被扩展是有点不太常见的,当然这个完全可以通过类标记成 final 实现。或者也可以更加复杂一些,就是通过内部包含了未知数量具体实现的工厂方法返回一个接口(Interface)。举个例子,想要使用一个叫做 Zoo 的类,但是这个类不能被继承,所以无法增加新的功能。

但是想追踪 Zoo 对象的额外信息,该怎么办? 假设需要记录每个对象的序列号,但是由于 Zoo 类并不包含这个属性,而且也不能扩展导致也不能增加这个属性。其实一点问题也没有,HashMap 完全可以解决上述的问题。

serialNumberMap.put(zoo, zooSerialNumber);

表面看上去没问题,但是 zoo 对象的强引用很有可能会引发问题。可以确信当一个 zoo 序列号不需要时,应该将这个条目从 map 中移除。如果没有移除的话,可能会导致内存泄露,亦或者手动移除时删除了正在使用的 zoo,会导致有效数据的丢失。其实这些问题很类似,这就是没有垃圾回收机制的语言管理内存时常遇到的问题。

另一个强引用可能带来的问题就是缓存,尤其是像图片这样的大文件的缓存。假设有一个程序需要处理用户提供的图片,通常的做法就是做图片数据缓存,因为从磁盘加载图片代价很大,并且同时也想避免在内存中同时存在两份一样的图片数据。

缓存被设计的目的就是避免去再次加载哪些不需要的文件。你会很快发现在缓存中会一直包含一个到已经指向内存中图片数据的引用。使用强引用会强制图片数据留在内存,这就需要决定什么时候图片数据不需要并且手动从缓存中移除,进而可以让垃圾回收器回收。因此再一次被强制做垃圾回收器该做的工作,并且人为决定是该清理到哪一个对象。

二、软引用(SoftReference)

软引用基本上和弱引用差不多,只是相比弱引用,它阻止垃圾回收器回收其指向的对象的能力强一些。如果一个对象是弱引用可到达,那么这个对象会被垃圾回收器接下来的回收周期销毁。但是如果是软引用可到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾回收器才会回收这些软引用可到达的对象。

由于软引用可到达的对象比弱引用可达到的对象滞留内存时间会长一些,可以利用这个特性来做缓存。这样的话,就可以节省很多事情,垃圾回收器会关心当前哪种可到达类型以及内存的消耗程度来进行处理。

三、弱引用(WeakReference)

Java 中的弱引用具体指的是java.lang.ref.WeakReference<T>类,以下是官方文档对它做的说明:
弱引用对象的存在不会阻止它所指向的对象被垃圾回收器回收。弱引用最常见的用途是实现规范映射(canonicalizing mappings,比如哈希表)。
假设垃圾回收器在某个时间点决定一个对象是弱可达的(weakly reachable)(也就是说当前指向它的全都是弱引用),这时垃圾回收器会清除所有指向该对象的弱引用,然后把这个弱可达对象标记为可终结(finalizable)的,这样它随后就会被回收。与此同时或稍后,垃圾回收器会把那些刚清除的弱引用放入创建弱引用对象时所指定的引用队列(Reference Queue)中。

弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用 WeakReference,垃圾回收器会决定引用的对象何时回收并且将对象从内存移除。创建弱引用写法如下:

WeakReference<Zoo> weakZoo = new WeakReference<Zoo>(zoo);

使用 weakZoo.get() 就可以得到真实的 Zoo 对象。因为弱引用不能阻挡垃圾回收器对其回收,当没有任何强引用到 zoo 对象时,使用 get 返回 null。
解决上述的 zoo 序列数记录的问题,最简单的办法就是使用 Java 内置的 WeakHashMap 类。WeakHashMap 和 HashMap 几乎一样,唯一的区别就是它的键(不是值!!!)使用 WeakReference。当 WeakHashMap 的键标记为垃圾的时候,这个键对应的条目就会自动被移除。这就避免了上面不需要的 Zoo 对象手动删除的问题。使用 WeakHashMap 可以很便捷地转为 HashMap 或者 Map。

一旦弱引用对象开始返回 null,该弱引用指向的对象就被标记成了垃圾。而这个弱引用对象(非其指向的对象)就没用了。通常这时候需要进行一些清理工作。比如 WeakHashMap 会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。

引用队列可以很容易地实现跟踪不需要的引用。当在构造 WeakReference 时传入一个 ReferenceQueue 对象,当该引用指向的对象被标记为垃圾的时候,这个引用对象会自动地加入到引用队列里面。接下来,就可以在固定的周期,处理传入的引用队列,比如做一些清理工作来处理这些没有用的引用对象。

理解:为什么使用弱引用?

现有一个熊猫类 Pandas,该类被设计为不可扩展的,此时想要为每只熊猫增加一个编号。一种解决方案是使用 HashMap<Pandas, Integer>。问题来了,如果已经不再需要一个 Pandas 对象存在于内存中(比如已经卖出了这件产品),假设指向它的引用为 pandaA,这时会给 pandaA 赋值为 null,然而 pandaA 过去指向的 Pandas 对象并不会被回收,因为它显然还被 HashMap 引用着。所以这种情况下,想要真正的回收一个 Pandas 对象,仅仅把它的强引用赋值为 null 是不够的,还要把相应的条目从 HashMap 中移除。“从 HashMap 中移除不再需要的条目”开发者必须附带该操作,然还是希望垃圾回收器知晓:在只有 HashMap 中的 key 在引用着 Pandas 对象的情况下,就可以回收相应的 Pandas 对象了。显然,使用弱引用能达成这个目的。只需要用一个指向 Pandas 对象的弱引用对象来作为 HashMap 中的 key 就可以了。

如何使用弱引用?

拿上面介绍的场景举例,使用一个指向 Pandas 对象的弱引用对象来作为 HashMap的key,如下定义该弱引用对象:

Pandas pandaA = new Pandas(...);
WeakReference<Pandas> weakPandaA = new WeakReference<>(pandaA);

现在,弱引用对象 weakPandaA 就指向了 Pandas 对象 pandaA。怎么通过 weakPandaA 获取它所指向的 Pandas 对象 pandaA 呢?很简单,只需要下面这句代码:

Pandas pandas= weakPandaA.get();

实际上,Java 类库提供了 WeakHashMap 类,使用该类,它的键自然就是弱引用对象,无需再手动包装原始对象。这样一来,当 pandaA 变为 null 时(表明它所引用的 Pandas 已经无需存在于内存中),这时指向该 Pandas 对象的就是弱引用对象 weakPandaA 了,显然此时相应的 Pandas 对象是弱可达的,所以指向它的弱引用会被清除,该 Pandas 对象随即会被回收,指向它的弱引用对象会进入引用队列中。

四、虚引用 (PhantomReference)

与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。

当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在对象析构或者垃圾回收真正发生之前。理论上,这个即将被回收的对象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用只有在其指向的对象从内存中移除掉之后才会加入到引用队列中。其get方法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。

虚引用使用场景主要由两个。它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后再继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了finalize方法的对象如果想要被回收掉,需要经历两个单独的垃圾回收周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析构之前,需要经历数量不确定的垃圾回收周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会出现烦人的内存溢出错误。

使用虚引用,上述情况将引刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生,所以其对象会在垃圾回收的第一个周期就将被清理掉。

显而易见,finalize方法不建议被重写。因为虚引用明显地安全高效,去掉finalize方法可以虚拟机变得明显简单。当然你也可以去重写这个方法来实现更多。这完全看个人选择。

五、引用队列(Reference Queue)

弱引用类(WeakReference)有两个构造函数:

//创建一个指向给定对象的弱引用
WeakReference(T referent) 
//创建一个指向给定对象并且登记到给定引用队列的弱引用
WeakReference(T referent, ReferenceQueue<? super T> q) 

可以看到第二个构造方法中提供了一个 ReferenceQueue 类型的参数,通过提供该参数,便把创建的弱引用对象注册到了一个引用队列上,这样当它被垃圾回收器清除时,就会把它送入这个引用队列中,便可以对这些被清除的弱引用对象进行统一管理。

六、总结

  1. 强引用(StrongReference):通常通过 new 来创建一个新对象时返回的引用就是一个强引用,若一个对象通过一系列强引用可到达,它就是强可达的(strongly reachable),那么它就不被回收。

  2. 软引用(SoftReference):软引用可达的对象在内存不充足时才会被回收,因此软引用要比弱引用“强”一些。

  3. 弱引用(WeakReference):弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用 WeakReference,垃圾回收器决定引用的对象何时回收并且将对象从内存移除。若一个对象是弱引用可达,无论当前内存是否充足它都会被回收

  4. 虚引用(PhantomReference):虚引用是 Java 中最弱的引用,那么它弱到什么程度呢?通过虚引用无法获取到被引用的对象,虚引用存在的唯一作用就是当它指向的对象被回收后,虚引用本身会被加入到引用队列中,用作记录它指向的对象已被回收。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容