Java理论和实践:用软引用阻止内存泄漏
来源:网络 更新时间:2014-12-2
在本文中,他将解释Reference对象的另外一种形式,即软引用(softreferences),用于帮助垃圾收集器管理内存使用和消除潜在的内存泄漏。
垃圾收集可以使Java程序不会出现内存泄漏,至少对于比较狭窄的“内存泄漏”定义来说如此,但是这并不意味着我们可以完全忽略Java程序中的对象生存期(lifetime)问题。当我们没有对对象生命周期(lifecycle)引起足够的重视或者破坏了管理对象生命周期的标准机制时,Java程序中通常就会出现内存泄漏。例如,上一次我们看到了,不能划分对象的生命周期会导致,在试图将元数据关联到瞬时对象时出现意外的对象保持。还有一些其他的情况可以类似地忽略或破坏对象生命周期管理,并导致内存泄漏。
对象游离
一种形式的内存泄漏有时候叫做对象游离(objectloitering),是通过清单1中的LeakyChecksum类来说明的,清单1中有一个getFileChecksum()方法用于计算文件内容的校验和。getFileChecksum()方法将文件内容读取到缓冲区中以计算校验和。一种更加直观的实现简单地将缓冲区作为getFileChecksum()中的本地变量分配,但是该版本比那样的版本更加“聪明”,不是将缓冲区缓存在实例字段中以减少内存churn。该“优化”通常不带来预期的好处;对象分配比很多人期望的更便宜。(还要注意,将缓冲区从本地变量提升到实例变量,使得类若不带有附加的同步,就不再是线程安全的了。直观的实现不需要将getFileChecksum()声明为synchronized,并且会在同时调用时提供更好的可伸缩性。)
清单1.展示“对象游离”的类
//BADCODE-DONOTEMULATE
publicclassLeakyChecksum{
privatebyte[]byteArray;
publicsynchronizedintgetFileChecksum(StringfileName){
intlen=getFileSize(fileName);
if(byteArray==null||byteArray.length<len)
byteArray=newbyte[len];
readFileContents(fileName,byteArray);
//calculatechecksumandreturnit
}
}
这个类存在很多的问题,但是我们着重来看内存泄漏。缓存缓冲区的决定很可能是根据这样的假设得出的,即该类将在一个程序中被调用许多次,因此它应该更加有效,以重用缓冲区而不是重新分配它。但是结果是,缓冲区永远不会被释放,因为它对程序来说总是可及的(除非LeakyChecksum对象被垃圾收集了)。更坏的是,它可以增长,却不可以缩小,所以LeakyChecksum将永久保持一个与所处理的最大文件一样大小的缓冲区。退一万步说,这也会给垃圾收集器带来压力,并且要求更频繁的收集;为计算未来的校验和而保持一个大型缓冲区并不是可用内存的最有效利用。
LeakyChecksum中问题的原因是,缓冲区对于getFileChecksum()操作来说逻辑上是本地的,但是它的生命周期已经被人为延长了,因为将它提升到了实例字段。因此,该类必须自己管理缓冲区的生命周期,而不是让JVM来管理。
软引用
弱引用如何可以给应用程序提供当对象被程序使用时另一种到达该对象的方法,但是不会延长对象的生命周期。Reference的另一个子类——软引用——可满足一个不同却相关的目的。其中弱引用允许应用程序创建不妨碍垃圾收集的引用,软引用允许应用程序通过将一些对象指定为“expendable”而利用垃圾收集器的帮助。尽管垃圾收集器在找出哪些内存在由应用程序使用哪些没在使用方面做得很好,但是确定可用内存的最适当使用还是取决于应用程序。如果应用程序做出了不好的决定,使得对象被保持,那么性能会受到影响,因为垃圾收集器必须更加辛勤地工作,以防止应用程序消耗掉所有内存。
高速缓存是一种常见的性能优化,允许应用程序重用以前的计算结果,而不是重新进行计算。高速缓存是CPU利用和内存使用之间的一种折衷,这种折衷理想的平衡状态取决于有多少内存可用。若高速缓存太少,则所要求的性能优势无法达到;若太多,则性能会受到影响,因为太多的内存被用于高速缓存上,导致其他用途没有足够的可用内存。因为垃圾收集器比应用程序更适合决定内存需求,所以应该利用垃圾收集器在做这些决定方面的帮助,这就是件引用所要做的。