7.3.4 弱引用
弱引用(weak reference)在强度上弱于软引用,用java.lang.ref.WeakReference类来表示。弱引用传递给垃圾回收器的信息是:在判断一个对象是否存活时,可以不考虑弱引用的存在。比如,指向一个对象的既有一个强引用,又有多个弱引用,当该强引用被移除之后,这个对象就可以被垃圾回收器回收,可以忽略指向该对象的弱引用。弱引用的存在不会影响垃圾回收器回收一个对象,还提供了一种方式可以引用到这个对象。如果一个对象既不是强引用可达,也不是软引用可达,同时可以通过弱引用来访问,则将该对象称为弱引用可达(weakly reachable)。
弱引用的重要作用是解决对象的存活时间过长的问题。在程序中,一个对象的实际存活时间应该与它的逻辑存活时间一样。从逻辑上来说,一个对象应该在某个方法调用完成之后就不再需要,可以对其进行垃圾回收。但是,如果仍然有其他的强引用存在,该对象的实际存活时间会长于逻辑存活时间,直到其他的强引用不再存在。这样的对象在程序中过多出现会导致虚拟机的内存占用率上升,最后产生OutOfMemoryError错误。要解决这样的问题,需要小心注意管理对象上的强引用。当不再需要引用一个对象时,显式地清除这些强引用。不过这会对开发人员提出更高的要求,代码编写起来也更加复杂。更好的办法是使用弱引用来代替强引用来引用这些对象。这样既可以引用对象,又可以避免强引用带来的问题。
比较典型的例子是在哈希表中使用弱引用。在代码清单7-9中,通过BookKeeper类来对图书及其借阅者进行管理。在内部实现中使用了一个HashMap类的对象来保存图书及其借阅者之间的对应关系。由于HashMap类的对象具有对所包含的键和值的对象的强引用,这会使Book类和User类对象的存活时间变得至少和HashMap类的对象本身一样长。当HashMap类的对象本身还存活时,其中所包含的Book类和User类的对象都无法被垃圾回收器回收。
代码清单7-9 HashMap类造成对象存活时间过长的示例
public class BookKeeper{
private Map<Book, Set<User>>books=new HashMap<>();
public void borrowBook(Book book, User user){
Set<User>users=null;
if(books.containsKey(book)){
users=books.get(book);
}
else{
users=new HashSet<User>();
books.put(book, users);
}
users.add(user);
}
public void returnBook(Book book, User user){
if(books.containsKey(book)){
Set<User>users=books.get(book);
users.remove(user);
}
}
}
解决这个问题的做法是使用弱引用来指向这些对象,而不是使用默认的强引用。因为这样使用Map接口的情况很多,Java标准库提供了java.util.WeakHashMap类来满足这种常见的需求。WeakHashMap类使用弱引用来指向其中所包含的键,而键对应的值对象仍然由强引用来指向。使用弱引用的好处是WeakHashMap类的对象本身对其中包含的键的弱引用不会影响键对象的垃圾回收。当键对象不存在其他类型更强的引用时,键对象会被从WeakHashMap类的对象中删除。对于代码清单7-9中存在的问题,只需要把books对应的Map接口的实现类换成WeakHashMap类即可。不过WeakHashMap类的对象中包含的值对象仍然由强引用来指向,因此不能在值对象中包含键对象的引用。这种循环引用会导致键对象无法被垃圾回收器回收。如果觉得对于值对象使用强引用不合适,可以在添加到WeakHashMap类的对象之前用一个WeakReference类的对象来包装它。