7.3.3 软引用
软引用(soft reference)在强度上弱于强引用,用java.lang.ref.SoftReference类来表示。如果一个对象不是强引用可达的,同时可以通过软引用来访问,那么将这个对象称为软引用可达(softly reachable)。软引用所要传递给垃圾回收器的信息是:软引用所指向的对象是可以在需要的时候被回收的。垃圾回收器会保证在抛出OutOfMemoryError错误之前,回收掉所有软引用可达的对象。通过软引用,垃圾回收器就可以在内存不足时释放软引用可达的对象所占的内存空间。程序所要做的是保证软引用可达的对象被垃圾回收器回收之后,程序也能正常工作。在之前介绍的图像编辑器打开文件的示例中,可以用强引用指向当前正在编辑的图片,而用软引用指向不处于编辑状态的其他已经打开的图片。这样当图像编辑器程序的内存不足时,垃圾回收器可以释放不处于编辑状态的图片所占的内存空间。当程序中需要引用占用内存比较大的对象时,可以考虑使用软引用来指向该对象。
下面通过一个示例来具体说明软引用的用法。代码清单7-8中的FileEditor类用来对多个文件进行编辑。出于性能方面的考虑,FileEditor类的对象会在内部缓存之前已经打开过的文件的数据内容,以方便用户在同时打开的多个文件之间进行快速切换。同时打开的文件过多会占用比较多的内存资源。因此,表示文件数据的FileData类使用软引用来指向包含文件数据的byte[]对象。当虚拟机的内存不足时,这些byte[]对象可以被垃圾回收器释放。这里需要注意FileData类的getData方法的实现中对软引用的使用方式。通过get方法获取软引用所指向的对象之后,需要判断这个对象是否还存活。如果get方法的返回值为null,那么说明对该对象的引用已经被清空,应该重新创建出相关的对象。
代码清单7-8 使用软引用的文件编辑器
public class FileEditor{
private static class FileData{
private Path filePath;
private SoftReference<byte[]>dataRef;
public FileData(Path filePath){
this.filePath=filePath;
this.dataRef=new SoftReference<byte[]>(new byte[0]);
}
public Path getPath(){
return filePath;
}
public byte[]getData()throws IOException{
byte[]dataArray=dataRef.get();
if(dataArray==null||dataArray.length==0){
dataArray=readFile();
dataRef=new SoftReference<byte[]>(dataArray);
dataArray=null;
}
return dataRef.get();
}
private byte[]readFile()throws IOException{
return Files.readAllBytes(filePath);
}
}
private FileData currentFileData;
Private Map<Path, FileData>openedFiles=new HashMap<>();
public void switchTo(String filePath){
Path path=Paths.get(filePath).toAbsolutePath();
if(openedFiles.containsKey(path)){
currentFileData=openedFiles.get(path);
}else{
currentFileData=new FileData(path);
openedFiles.put(path, currentFileData);
}
}
public void useFile()throws IOException{
if(currentFileData!=null){
System.out.println(String.format("当前文件%1$s的大小为%2$d",currentFileData.getPath(),currentFileData.getData().length));
}
}
}
为了测试代码清单7-8中软引用在实际运行中的效果,使用FileEditor类的对象依次打开某个目录下包含的大小各异的多个文件,同时通过虚拟机的启动参数“-Xmx”把虚拟机所用的堆内存的最大值设置为一个相对较小的值。在运行时会发现,虽然虚拟机可用堆内存的最大值远小于所处理的所有文件的大小的总和,但是程序在运行中也不会抛出OutOfMemoryError错误。这是因为当虚拟机中内存不足时,软引用指向的byte[]对象会被释放,从而可以腾出内存空间供之后的文件操作使用。如果使用强引用指向byte[]对象,在打开目录下的部分文件之后,就会出现OutOfMemoryError错误,这是因为虚拟机的堆内存不足以容纳全部文件的内容,而垃圾回收器又无法释放强引用指向的byte[]对象。