当在Java 2平台中首次引入包含SoftReference
, WeakReference
和PhantomReference
类的java.lang.ref
软件包时,其实用性可能被夸大了。 它包含的类可能是有用的,但是它们具有某些局限性,从而使它们的吸引力有所降低,并使它们的应用程序非常特定于一组已定义的问题。
垃圾收集概述
引用类的主要特征是能够引用仍可以由垃圾收集器回收的对象。 在引入参考类之前,只有强参考可用。 例如,以下代码行显示了一个强引用obj
:
Object obj = new Object();
引用obj
引用存储在堆中的对象。 只要obj
引用存在,垃圾收集器就永远不会释放用于保存该对象的存储。
当obj
超出范围或被显式分配为null
,假定没有其他引用,则该对象可用于收集。 但是,需要注意的一个重要细节是,仅因为对象可用于收集并不意味着将被给定的垃圾收集器运行回收。 由于垃圾收集算法各不相同,因此某些算法对较早的,寿命较长的对象的分析频率比短期的对象低。 因此,可能无法回收可用于收集的对象。 如果程序在垃圾回收器释放对象之前结束,则可能发生这种情况。 因此,最重要的是,您永远不能保证垃圾收集器将收集可用的对象。
分析参考类时,此信息很重要。 它们是解决特定问题的有用类,尽管由于垃圾回收的性质,它们可能未如您最初想象的那样有用。 软引用,弱引用和幻像引用对象提供了三种不同的方式来引用堆对象,而不会阻止其收集。 每种类型的引用对象都有不同的行为,并且它们与垃圾收集器的交互也有所不同。 此外,新的参考类别都代表比典型的强参考更“弱”的参考形式。 此外,内存中的对象可以由多个引用来引用,这些引用可以是强,软,弱或幻像。 在继续进行之前,让我们看一下一些术语:
- 高度可达:可以通过强引用访问的对象。
- 软可访问性:不可强访问的对象,可以通过软引用对其进行访问。
- 弱可达性:无法强弱或柔弱可达的对象,可以通过弱引用进行访问。
- 幻影可到达:无法强定,柔弱或难以到达的对象已完成,可以通过幻像引用进行访问。
- 清除:将引用对象的referent字段设置为
null
并在堆中声明该引用类称为finalizable的对象 。
SoftReference类
SoftReference
类的典型用法是用于内存敏感的缓存。 SoftReference
的想法是,您持有对对象的引用,并确保在JVM报告内存不足情况之前将清除所有软引用。 关键是,当垃圾收集器运行时,它可能释放也可能不释放软可访问对象。 对象是否被释放取决于垃圾收集器的算法以及收集器运行时可用的内存量。
WeakReference类
WeakReference
类的典型用法是用于规范化映射。 此外,弱引用对于否则将生存很长时间并且重新创建的成本也不高的对象很有用。 关键是,当垃圾收集器运行时,如果遇到一个弱可达的对象,它将释放WeakReference
的对象。 但是请注意,在找到并释放弱可达对象之前,可能需要运行多次垃圾收集器。
PhantomReference类
PhantomReference
类仅用于跟踪引用对象的即将来临的集合。 这样,它可以用于执行事前清理操作。 PhantomReference
必须与ReferenceQueue
类一起使用。 ReferenceQueue
是必需的,因为它用作通知机制。 当垃圾回收器确定对象是幻影可访问的时, PhantomReference
对象将放置在其ReferenceQueue
。 的的配售PhantomReference
上的物体ReferenceQueue
是你通知的对象PhantomReference
对象称已经完成,准备进行收集。 这使您可以在回收对象内存之前立即采取措施。
垃圾收集器和参考交互
每次垃圾收集器运行时,它都可以选择释放不再强烈可达的对象内存。 如果垃圾收集器发现了可以软访问的对象,则会发生以下情况:
-
SoftReference
对象的referent字段设置为null
,从而使其不再引用heap
对象。 - 由
SoftReference
引用的heap
对象被声明为finalizable
。 - 运行
heap
对象的finalize()
方法并释放其内存时,如果存在,则将SoftReference
对象添加到其ReferenceQueue
。
如果垃圾收集器发现了一个弱可达的对象,则会发生以下情况:
-
WeakReference
对象的referent字段设置为null
,从而使其不再引用heap
对象。 - 已将
WeakReference
的heap
对象声明为finalizable
。 - 当运行
heap
对象的finalize()
方法并释放其内存时,WeakReference
对象将添加到其ReferenceQueue
(如果存在)中。
如果垃圾收集器发现了幻像可访问的对象,则会发生以下情况:
- 由
PhantomReference
引用的heap
对象被声明为finalizable
。 - 与软引用和弱引用不同,
PhantomReference
在释放堆对象之前被添加到其ReferenceQueue
。 (请记住,必须使用关联的ReferenceQueue
创建所有PhantomReference
对象。)这允许在回收堆对象之前执行操作。
考虑清单1中的代码。图1说明了此代码的执行。
//Create a strong reference to an object
MyObject obj = new MyObject(); //1
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue(); //2
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq); //3
图1.执行清单1中的第1、1、2、2和3行代码后的对象布局
图1显示了每行代码执行之后对象的状态。 第// 1行创建对象MyObject
,而第// 2行创建ReferenceQueue
对象。 // 3行创建WeakReference
其引用MyObject
和ReferenceQueue
的WeakReference
对象。 请注意,每个对象引用obj
, rq
和wr
都是强引用。 要利用这些引用类,必须通过将obj
设置为null
来打破对MyObject
对象的强引用。 回想一下,如果您不这样做,则将永远不会回收对象MyObject
,从而减轻了引用类的任何好处。
每个参考类都有一个get()
方法, ReferenceQueue
类有一个poll()
方法。 get()
方法将对引用的引用返回给引用对象。 在PhantomReference
上调用get()
始终返回null
。 这是因为PhantomReference
仅用于跟踪集合。 poll()
方法返回已添加到队列中的引用对象,如果队列上没有任何内容,则返回null
。 因此,清单1执行后调用get()
和poll()
的结果将是:
wr.get(); //returns reference to MyObject
rq.poll(); //returns null
现在,假设垃圾收集器正在运行。 由于未释放MyObject
对象,因此get()
和poll()
方法返回相同的值。 obj
仍然对其有很强的引用。 实际上,对象布局保持不变,如图1所示。但是,请考虑以下代码:
obj = null;
System.gc(); //run the collector
执行此代码后,对象布局如图2所示:
图2. obj = null之后的对象布局; 和垃圾收集器运行
现在,调用get()
和poll()
产生不同的结果:
wr.get(); //returns null
rq.poll(); //returns a reference to the WeakReference object
这种情况表明,原来由WeakReference
对象保存其引用的MyObject
对象不再可用。 这意味着垃圾回收器释放了MyObject
的内存,从而将WeakReference
对象放置在其ReferenceQueue
。 因此,您知道当WeakReference
或SoftReference
类的get()
方法返回null
时,已声明一个对象可以finalizable
并且可能但不一定要声明为对象。 仅当完成完成并收集heap
对象的内存时, WeakReference
或SoftReference
才会放置在与其关联的ReferenceQueue
。 清单2显示了一个完整的工作程序,演示了其中一些原理。 该代码是相对不言自明的,带有许多注释和打印语句以帮助理解。
import java.lang.ref.*;
class MyObject
{
protected void finalize() throws Throwable
{
System.out.println("In finalize method for this object: " +
this);
}
}
class ReferenceUsage
{
public static void main(String args[])
{
hold();
release();
}
public static void hold()
{
System.out.println("Example of incorrectly holding a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);
System.out.println("The weak reference is " + wr);
//Check to see if it's on the ref queue yet
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
System.out.println("Calling GC");
System.gc();
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}
public static void release()
{
System.out.println("");
System.out.println("Example of correctly releasing a strong " +
"reference");
//Create an object
MyObject obj = new MyObject();
System.out.println("object is " + obj);
//Create a reference queue
ReferenceQueue rq = new ReferenceQueue();
//Create a weakReference to obj and associate our reference queue
WeakReference wr = new WeakReference(obj, rq);
System.out.println("The weak reference is " + wr);
//Check to see if it's on the ref queue yet
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
System.out.println("Set the obj reference to null and call GC");
obj = null;
System.gc();
System.out.println("Polling the reference queue returns " +
rq.poll());
System.out.println("Getting the referent from the " +
"weak reference returns " + wr.get());
}
}
用法和成语
这些类背后的思想是在应用程序运行期间避免将对象固定在内存中。 取而代之的是,您轻柔地,虚弱地或虚幻地引用一个对象,从而使垃圾回收器可以选择释放它。 当您要最小化应用程序在其生命周期内使用的堆内存量时,此用法可能会很有用。 您必须记住,使用这些类不能维护对对象的强引用。 如果这样做,您将浪费课程提供的任何好处。
另外,您必须使用正确的编程习惯来检查收集器是否在使用对象之前对其进行了回收,如果已回收,则必须首先重新创建该对象。 可以使用不同的编程习惯来完成此过程。 选择错误的习惯用法可能会给您带来麻烦。 考虑清单3中的代码惯用法,以从WeakReference
检索被引用对象:
obj = wr.get();
if (obj == null)
{
wr = new WeakReference(recreateIt()); //1
obj = wr.get(); //2
}
//code that works with obj
在研究了这段代码之后,请考虑清单4中的备用代码惯用法,以便从WeakReference
检索被引用对象:
obj = wr.get();
if (obj == null)
{
obj = recreateIt(); //1
wr = new WeakReference(obj); //2
}
//code that works with obj
比较这两种习惯用法,看看您是否可以确定保证可以使用哪种方法,而哪种不能保证。 不能保证清单3中概述的惯用法始终都有效,但是清单4的惯用法是可以的。 清单3中的惯用法不足的原因是, if
块的主体完成,则不能保证obj
不为null。 考虑如果垃圾回收器在清单3的// 1行之后执行,而在// 2行执行之前发生了什么。 recreateIt()
方法重新创建了对象,但是它是由WeakReference
而不是强引用引用的。 因此,如果收集器在第// 2行将强引用附加到重新创建的对象之前运行,则该对象将丢失,并且wr.get()
返回null
。
清单4没有这个问题,因为// 1行重新创建了该对象并为其指定了一个强引用。 因此,如果垃圾收集器在此行之后但在/// 2之前运行,则将不会回收该对象。 然后,在第// 2行创建对obj
的WeakReference
。 在if
块之后使用obj
之后,应将obj
设置为null
以使垃圾收集器可以回收该对象以充分利用弱引用。 清单5显示了一个完整的程序,演示了刚刚描述的习惯用法。 (要运行此程序,程序运行目录中必须存在一个“ temp.fil”文件。)
import java.io.*;
import java.lang.ref.*;
class ReferenceIdiom
{
public static void main(String args[]) throws FileNotFoundException
{
broken();
correct();
}
public static FileReader recreateIt() throws FileNotFoundException
{
return new FileReader("temp.fil");
}
public static void broken() throws FileNotFoundException
{
System.out.println("Executing method broken");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);
System.out.println("wr refers to object " + wr.get());
System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj.
obj = null;
System.gc();
System.out.println("wr refers to object " + wr.get());
//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it
in a WeakReference");
wr = new WeakReference(recreateIt());
System.gc(); //FileReader object is NOT pinned...there is no
//strong reference to it. Therefore, the next
//line can return null.
obj = (FileReader)wr.get();
}
System.out.println("wr refers to object " + wr.get());
}
public static void correct() throws FileNotFoundException
{
System.out.println("");
System.out.println("Executing method correct");
FileReader obj = recreateIt();
WeakReference wr = new WeakReference(obj);
System.out.println("wr refers to object " + wr.get());
System.out.println("Now, clear the reference and run GC");
//Clear the strong reference, then run GC to collect obj
obj = null;
System.gc();
System.out.println("wr refers to object " + wr.get());
//Now see if obj was collected and recreate it if it was.
obj = (FileReader)wr.get();
if (obj == null)
{
System.out.println("Now, recreate the object and wrap it
in a WeakReference");
obj = recreateIt();
System.gc(); //FileReader is pinned, this will not affect
//anything.
wr = new WeakReference(obj);
}
System.out.println("wr refers to object " + wr.get());
}
}
摘要
如果在正确的情况下使用Reference类,则可能会很有用。 但是,由于它们依赖于垃圾收集器有时无法预测的行为,因此削弱了它们的实用性。 它们的有效使用还取决于应用正确的编程习惯。 了解这些类的实现方式以及如何对其编程是至关重要的。
翻译自: https://www.ibm.com/developerworks/java/library/j-refs/index.html