`
jaesonchen
  • 浏览: 313030 次
  • 来自: ...
社区版块
存档分类
最新评论

译文:理解Java中的弱引用

 
阅读更多

不久之前,我面试了一些求职Java高级开发工程师的应聘者。我常常会面试他们说,“你能给我介绍一些Java中得弱引用吗?”,如果面试者这样说,“嗯,是不是垃圾回收有关的?”,我就会基本满意了,我并不期待回答是一篇诘究本末的论文描述。

然而事与愿违,我很吃惊的发现,在将近20多个有着平均5年开发经验和高学历背景的应聘者中,居然只有两个人知道弱引用的存在,但是在这两个人之中只有一个人真正了解这方面的知识。在面试过程中,我还尝试提示一些东西,来看看有没有人突然说一声“原来是这个啊”,结果很是让我失望。我开始困惑,为什么这块的知识如此不被重视,毕竟弱引用是一个很有用途的特性,况且这个特性已经在7年前 Java 1.2发布时便引入了。

好吧,这里我不期待你看完本文之后成为一个弱引用方面的专家,但是我认为至少你应该了解什么是弱引用,如何使用它们,并且什么场景使用。既然它们是一些不知名的概念,我简单就着前面的三个问题来说明一下。

强引用(Strong Reference)

强引用就是我们经常使用的引用,其写法如下

1
StringBuffer buffer = new StringBuffer();

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

但是强引用如此之强

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

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

1
serialNumberMap.put(widget, widgetSerialNumber);

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

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

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

弱引用(Weak Reference)

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

1
WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);

使用weakWidget.get()就可以得到真实的Widget对象,因为弱引用不能阻挡垃圾回收器对其回收,你会发现(当没有任何强引用到widget对象时)使用get时突然返回null。

解决上述的widget序列数记录的问题,最简单的办法就是使用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方法可以虚拟机变得明显简单。当然你也可以去重写这个方法来实现更多。这完全看个人选择。

总结

我想看到这里,很多人开始发牢骚了,为什么你要讲一个过去十年的老古董API呢,好吧,以我的经验看,很多的Java程序员并不是很了解这个知识,我认为有一些深入的理解是很必要的,同时我希望大家能从本文中收获一些东西。

原文信息

附注信息

本文涉及到很多概念对于初次接触的人相对比较难以理解,建议结合英文原文进行研究。

Java高阶推荐

分享到:
评论

相关推荐

    《Java核心技术 卷I:基础知识(原书第8版)》勘误表

    这些勘误反映了Java编程中的一些核心概念,包括版本控制、数据类型、文件操作、数组、多维数组、变量初始化、代码示例、类库引用、线程安全和GUI组件的使用。修正这些错误有助于读者更好地理解和应用Java语言。学习...

    Java编程思想--外文文献原文及译文(可编辑).doc

    但在Java中,你操作的是对象的“引用”,可以想象成通过遥控器控制电视——引用是电视的对象,只要持有这个引用,就与电视保持连接。当你改变频道或调节音量时,实际上是在操作引用。 Java中的类和对象是其核心概念...

    Java虚拟机规范(Java SE 7).pdf

    规范中的内容对于理解Java虚拟机如何工作至关重要,但要对虚拟机的性能进行调优和分析,则需要更深入的资料。译者序中提到,通过阅读《Java Performance》和《Oracle JRockit: The Definitive Guide》等相关书籍,...

    计算机专业论文译文-对象的传递与返回

    首先,让我们澄清一点:当我们在Java中“传递”一个对象时,实际上我们传递的是对象的引用,而不是对象本身。这与某些编程语言(如C++)中的行为有所不同,C++允许直接操作指针,而Java则限制了这种操作。Java中的每...

    Java语言编码规范

    ### Java语言编码规范 #### 1.1 为什么要有编码规范(Why Have Code Conventions) 编码规范对于程序员而言尤为重要,其重要性体现在以下几个方面: 1. **提高代码可读性**:一致性的编码风格能够帮助团队成员更快...

    Pro+Spring+5

    6. 出版信息:文件中提供了ISBN号和DOI,这表明书籍可以通过正规渠道进行购买和引用。读者可以通过提供的链接访问更多关于书籍的信息。 7. 编辑和出版团队:文件中提到了管理总监、编辑总监、收购编辑等职位,说明...

    侦探翻译(引用百度地图api)

    例如,翻译结果可能包含原文、译文、源语言和目标语言的标识等信息,开发者需要解析这些数据,以便在应用中显示。 在实际应用中,为了提高用户体验,可能还需要考虑一些额外的优化策略,比如缓存最近的翻译结果以...

    JAVA语言考试系统的设计与实现(论文+源代码+文献综述+外文翻译+开题报告).rar

    文献综述环节,作者会引用和分析相关领域的研究文献,比较不同考试系统的优缺点,以及JAVA语言在考试系统中的应用实例,为自己的设计方案提供理论依据。这有助于读者理解系统设计的创新点和改进之处。 外文翻译可能...

    using thymeleaf.pdf

    ### Thymeleaf 3.0.5.RELEASE 使用教程 #### 1. Thymeleaf简介 ##### 1.1 Thymeleaf是什么?...通过本文的学习,希望能够帮助读者更好地理解和掌握Thymeleaf的使用方法,从而在实际项目中发挥其最大的价值。

    基于J2EE的B2C电子商务系统开发(设计说明书+源码).zip

    4. **译文.doc** - 如果这份文档是针对特定技术或理论的翻译,那么它可以帮助读者理解项目中引用的外部技术或概念。 5. **代码** - 与"程序"类似,这可能包含具体的Java类、Servlet、JSP页面等,用于实现系统功能。...

    英文参考文献格式

    ### 英文参考文献格式详解 #### 一、参考文献的类型标识 在撰写学术论文时,正确引用参考文献是非常重要的。为了使读者能够准确找到所引用的...希望以上的介绍能够帮助大家更好地理解和掌握英文参考文献的格式规范。

    Spring相关的外文文献和翻译(毕设论文必备)

    Spring框架是Java开发中的核心组件,它以其模块化、易用性和灵活性著称,广泛应用于企业级应用开发。本文将围绕“Spring相关的外文文献和翻译”这一主题,深入探讨Spring框架的重要知识点,并结合毕设论文的需求,为...

    网站论文格式及注意事项

    【网站论文格式及注意事项】 在撰写学术论文时,特别是针对网站设计或开发的毕业设计,遵循正确的格式规范至关重要。...在使用JSP、Java、MVC和Servlet等技术的网站设计项目中,这些规范同样适用。

    计算机专业开题报告+外文翻译+文献综述

    在翻译过程中,要准确把握原文的含义,确保译文的准确性和专业性。对于"大学外语四六级成绩管理系统"和"基于WEB的毕业设计选题系统开发",可能涉及的外文文献可能涵盖在线教育、项目管理、数据安全等方面,翻译时需...

Global site tag (gtag.js) - Google Analytics