Java中的四种引用

来源:互联网 发布:淘宝网电脑旺旺打不开 编辑:程序博客网 时间:2024/06/05 15:58

自从去年9月份写完一篇博客后,就陷入纠结的项目当中去,第一次接手商业项目,很多事都要慢慢的去学习,再加上个人的懒惰导致博

客没有坚持写下去,作为程序员深感惭愧啊!现在项目到收尾阶段了,有时间来看看书并做下来写博客了。

今天想写一个在Java中十分古老的API--强引用和弱引用,但是确实在之前很多时候都没有去深思,想清楚。借这个机会即是学习也是分享。

强引用(Strong Reference)

其写法是:

StringBuffer buffer = new StringBuffer();

上面创建了一个StringBuffer对象,并将这个对象的(强)引用存到变量buffer中。强引用最重要的就是它能够让引用变得强(Strong),

这就决定了它和垃圾回收器交互。具体来说,如果一个对象通过一串强引用链接可到达(Strongly reachable),它是不会被回收的。如果你

不想让你正在使用的对象被回收,这就正是你所需要的。 

  但是这种引用关系实在是太强了,在一个项目中,将类或者组件设置成不可扩展的话那实在是太糟糕了,所以我们需要想一些其他的办法

来解决这样的强引用带来的问题。其中一个方法就是可以将这个类设置成为final实现的或者其他更为复杂的实现,如就是通过内部包含了未知

数量具体实现的工厂方法返回一个接口(Interface)。

  举个例子说,接着我上一篇写工厂模式的例子吧,一个糕点类Pastry,我将其实现为final的,此时需要注意的是,这个类不能被继承了,

因此无法添加新的功能。但是我想跟踪Pastry的额外的一些属性怎么办,比如我给每一个糕点设置一个编号,以便我统计这一天到底生产了

并卖出去多少个糕点,但是由于Pastry并不包含这个属性,我又不能去扩展它,怎么办呢?

也许HashMap可以帮助我实现这个功能:

pastryNumberMap.put(pastry, pastryNo);

这样看起来可能没什么问题,包括我在项目中有时候也是这样做的,但是我们来仔细分析一下这个地方:

我们可以确信当一个pastry编号不需要时,我们应该将这个条目从map中移除。如果我们没有移除的话,可能会导致内存泄露,亦或者我们

手动移除时删除了我们正在 使用的pastry,会导致有效数据的丢失。这就是Pastry对象的强引用很有可能会引发问题。其实这些问题很类似,

这就是没有垃圾回收机制的语言管理内存时常遇到的问题。但是我们不用去担心这个问题,因为我们使用的时具有垃圾回收机制的Java语言。

但正是因为如此,也就有了我所关注的,由于对Pastry对象的强引用引发的问题--我们被强制做垃圾回收器该做的工作,并且人为决定是该清

理到哪一个对象。

另一个强引用可能带来的问题就是缓存,尤其是像图片这样的大文件的缓存。假设你有一个程序需要处理用户提供的图片,通常的做法就

是做图片数据缓存,因为从磁 盘加载图片代价很大,并且同时我们也想避免在内存中同时存在两份一样的图片数据。缓存被设计的目的就是

避免我们去再次加载哪些不需要的文件。你会很快发现在缓存 中会一直包含一个到已经指向内存中图片数据的引用。使用强引用会强制图片

数据留在内存,这就需要你来决定什么时候图片数据不需要并且手动从缓存中移除,进而可以 让垃圾回收器回收。此时我又再一次被强制做

垃圾回收器该做的工作,并且人为决定是该清理到哪一个对象。

真是因为这样,才有了下面的讨论--弱引用(Weak Reference)

弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用Weak Reference,垃圾回收器会帮你来决定引用的对象何时回收并

将对象从内存移除。

创建弱 引用如下

WeakReference<Pastry> weakPastry = new WeakReference<Pastry>(pastry);

此时使用weakPastry.get()就可以得到真实的Pastry对象,这个过程你会发现一个现象使用get有可能会发回null,这是因为因为弱引用不

能阻挡垃圾回收器对其回收。解决上述的Pastry序列数记录的问题,最简单的办法就是使用Java内置的WeakHashMap类。WeakHashMap和

HashMap几乎一样,唯一的区别就是它的键(不是值!! 使用WeakReference引用,当WeakHashMap的键标记为垃圾的时候,这个键对应的条

目就会自动被移除。这就避免上面不需要的Widget对象手动删除的问题。使用WeakHashMap可以很便捷地转为HashMap或者Map。

我们再看一个新的东西:引用队列(Reference Queue)

一旦弱引用对象开始返回null,该弱引用指向的对象就被标记成了垃圾。而这个弱引用对象(非其指向的对象)就没有什么用了。通常这时

候需要进行一些清理工作。比如WeakHashMap会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。引用队列可以很容易

地实现跟踪不需要的引用。当你在构造WeakReference 时传入一个ReferenceQueue对象,当该引用指向的对象被标记为垃圾的时候,这个引

用对象会自动地加入到引用队列里面。接下来,你就可以在固定的周期,处理传入的 引用队列,比如做一些清理工作来处理这些没有用的引用对象。

关于Java中的引用一共有四种:从强到弱它们分别是,强引用,软引用,弱引用和虚引用。

软引用(Soft Reference)

软引用基本上和弱引用差不多,只是相比弱引用,它阻止垃圾回收期回收其指向的对象的能力强一些。如果一个对象是弱引用可到达,那么

这个对象会被垃圾回收器接 下来的回收周期销毁。但是如果是软引用可以到达,那么这个对象会停留在内存更时间上长一些。当内存不足时垃圾

回收器才会回收这些软引用可到达的对象。由于软引用 可到达的对象比弱引用可达到的对象滞留内存时间会长一些,我们可以利用这个特性来做缓存。

这样的话,你就可以节省了很多事情,垃圾回收器会关心当前哪种可到达类 型以及内存的消耗程度来进行处理。

虚引用 (Phantom Reference)

与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。它的唯一作用就是当其指向的对象被回收之后,

自己被加入到引 用队列,用作记录该引用指向的对象已被销毁。当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在

对象析构或者垃圾回收真正发生之 前。理论上,这个即将被回收的对象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用

只有在其指向的对象从内存中移除掉之后才会加入 到引用队列中。其get方法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。

虚引用使用场景主要由两个

第一点:它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。

当你确定一个图片 数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。

第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了finalize

方法的对象如果 想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在析构过程中

仍有微弱的可能这个对象会重 新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析

构之前,需要经历数量不确定的垃圾收集周 期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是

会出现烦人的内存溢出错误。使用虚引用,上述情况将引 刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,

对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生, 所以其对象会在垃圾回收的第一个周期就将被清理掉。显而易见,finalize方法不建议\

被重写。因为虚引用明显地安全高效,去掉finalize方法可以虚拟机变得明显简单。




0 0