在本文中,他将解释 Reference 对象的另外一种形式,即软引用(soft references),用于帮助垃圾收集器管理内存使用和消除潜在的内存泄漏。
垃圾收集可以使 Java 程序不会出现内存泄漏,至少对于比较狭窄的 “内存泄漏” 定义来说如此,但是这并不意味着我们可以完全忽略 Java 程序中的对象生存期(lifetime)问题。当我们没有对对象生命周期(lifecycle)引起足够的重视或者破坏了管理对象生命周期的标准机制时,Java 程序中通常就会出现内存泄漏。例如,上一次 我们看到了,不能划分对象的生命周期会导致,在试图将元数据关联到瞬时对象时出现意外的对象保持。还有一些其他的情况可以类似地忽略或破坏对象生命周期管理,并导致内存泄漏。
对象游离
一种形式的内存泄漏有时候叫做对象游离(object loitering),是通过清单 1 中的 LeakyChecksum 类来说明的,清单 1 中有一个 getFileChecksum() 方法用于计算文件内容的校验和。getFileChecksum() 方法将文件内容读取到缓冲区中以计算校验和。一种更加直观的实现简单地将缓冲区作为 getFileChecksum() 中的本地变量分配,但是该版本比那样的版本更加 “聪明”,不是将缓冲区缓存在实例字段中以减少内存 churn。该 “优化”通常不带来预期的好处;对象分配比很多人期望的更便宜。(还要注意,将缓冲区从本地变量提升到实例变量,使得类若不带有附加的同步,就不再是线程安全的了。直观的实现不需要将 getFileChecksum() 声明为 synchronized,并且会在同时调用时提供更好的可伸缩性。)
清单 1. 展示 “对象游离” 的类
// BAD CODE - DO NOT EMULATE
public class LeakyChecksum {
private byte[] byteArray;
public synchronized int getFileChecksum(String fileName) {
int len = getFileSize(fileName);
if (byteArray == null || byteArray.length < len)
byteArray = new byte[len];
readFileContents(fileName, byteArray);
// calculate checksum and return it
}
}
这个类存在很多的问题,但是我们着重来看内存泄漏。缓存缓冲区的决定很可能是根据这样的假设得出的,即该类将在一个程序中被调用许多次,因此它应该更加有效,以重用缓冲区而不是重新分配它。但是结果是,缓冲区永远不会被释放,因为它对程序来说总是可及的(除非 LeakyChecksum 对象被垃圾收集了)。更坏的是,它可以增长,却不可以缩小,所以 LeakyChecksum 将永久保持一个与所处理的最大文件一样大小的缓冲区。退一万步说,这也会给垃圾收集器带来压力,并且要求更频繁的收集;为计算未来的校验和而保持一个大型缓冲区并不是可用内存的最有效利用。
LeakyChecksum 中问题的原因是,缓冲区对于 getFileChecksum() 操作来说逻辑上是本地的,但是它的生命周期已经被人为延长了,因为将它提升到了实例字段。因此,该类必须自己管理缓冲区的生命周期,而不是让 JVM 来管理。