强引用(Strong Reference)
概念
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
Object o = new Object();
当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 如果不使用时,要通过如下方式来弱化引用:
o = null; // 帮助垃圾收集器回收此对象
例子
private transient Object[] elementData;
public void clear() {
modCount++;
// Let gc do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
引用队列(Reference Queue)
概念
一旦弱引用对象开始返回null,该弱引用指向的对象就被标记成了垃圾。
而这个弱引用对象(非其指向的对象)就没有什么用了。通常这时候需要进行一些清理工作。
比如WeakHashMap会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。
引用队列可以很容易地实现跟踪不需要的引用。
当你在构造WeakReference时传入一个ReferenceQueue对象,当该引用指向的对象被标记为垃圾的时候,这个引用对象会自动地加入到引用队列里面。
接下来,你就可以在固定的周期,处理传入的引用队列,比如做一些清理工作来处理这些没有用的引用对象。
软引用(Soft Reference)
概念
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。
只要垃圾回收器没有回收它,该对象就可以被程序使用。
实例
String str = new String("abc"); // 强引用
SoftReference<String> softRef = new SoftReference<String>(str); // 软引用
应用场景
软引用可用来实现内存敏感的高速缓存。
虚引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。
(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
Browser prev = new Browser(); // 获取页面进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get() != null){
rev = (Browser) sr.get(); // 还没有被回收器回收,直接获取
} else {
prev = new Browser(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
建议
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(Weak Reference)
概念
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
代码
String str = new String("abc");
WeakReference<String> abcWeakRef = new WeakReference<String>(str);
str = null;
使用场景
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。
内置工具
在Java集合框架系列文章的最后,笔者打算介绍一个特殊的成员:WeakHashMap,从名字可以看出它是某种 Map。 它的特殊之处在于 WeakHashMap 里的 entry 可能会被 GC 自动删除,即使程序员没有调用 remove() 或者 clear() 方法。
会发生什么?
更直观的说,当使用 WeakHashMap 时,即使没有显示的添加或删除任何元素,也可能发生如下情况:
-
调用两次size()方法返回不同的值;
-
两次调用isEmpty()方法,第一次返回false,第二次返回true;
-
两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key;
-
两次调用get()方法,第一次返回一个value,第二次返回null,尽管两次使用的是同一个对象。
疑问
使用者遇到这样的集合还不崩溃了。。。
其实不然,WeekHashMap 的这个特点特别适用于需要缓存的场景。
在缓存场景下,由于内存是有限的,不能缓存所有对象;对象缓存命中可以提高系统效率,但缓存 MISS 也不会造成错误,因为可以通过计算重新得到。
虚引用(Phantom Reference)
概念
与软引用,弱引用不同,虚引用指向的对象十分脆弱,我们不可以通过get方法来得到其指向的对象。
它的唯一作用就是当其指向的对象被回收之后,自己被加入到引用队列,用作记录该引用指向的对象已被销毁。
当弱引用的指向对象变得弱引用可到达,该弱引用就会加入到引用队列。这一操作发生在对象析构或者垃圾回收真正发生之前。理论上,这个即将被回收的对象是可以在一个不符合规范的析构方法里面重新复活。但是这个弱引用会销毁。虚引用只有在其指向的对象从内存中移除掉之后才会加入到引用队列中。其get方法一直返回null就是为了阻止其指向的几乎被销毁的对象重新复活。
虚引用使用场景主要由两个。它允许你知道具体何时其引用的对象从内存中移除。而实际上这是Java中唯一的方式。这一点尤其表现在处理类似图片的大文件的情况。当你确定一个图片数据对象应该被回收,你可以利用虚引用来判断这个对象回收之后在继续加载下一张图片。这样可以尽可能地避免可怕的内存溢出错误。
第二点,虚引用可以避免很多析构时的问题。finalize方法可以通过创建强引用指向快被销毁的对象来让这些对象重新复活。然而,一个重写了finalize方法的对象如果想要被回收掉,需要经历两个单独的垃圾收集周期。在第一个周期中,某个对象被标记为可回收,进而才能进行析构。但是因为在析构过程中仍有微弱的可能这个对象会重新复活。这种情况下,在这个对象真实销毁之前,垃圾回收器需要再次运行。因为析构可能并不是很及时,所以在调用对象的析构之前,需要经历数量不确定的垃圾收集周期。这就意味着在真正清理掉这个对象的时候可能发生很大的延迟。这就是为什么当大部分堆被标记成垃圾时还是会出现烦人的内存溢出错误。
使用虚引用,上述情况将引刃而解,当一个虚引用加入到引用队列时,你绝对没有办法得到一个销毁了的对象。因为这时候,对象已经从内存中销毁了。因为虚引用不能被用作让其指向的对象重生,所以其对象会在垃圾回收的第一个周期就将被清理掉。
显而易见,finalize方法不建议被重写。因为虚引用明显地安全高效,去掉finalize方法可以虚拟机变得明显简单。当然你也可以去重写这个方法来实现更多。这完全看个人选择。
总结
序号 | 引用类型 | GC 回收时间 | 用途 | 生存时间 |
---|---|---|---|---|
1 | 强引用 | never | 对象的一般状态 | JVM 终止运行时 |
2 | 软引用 | 内存不足时 | 对象的一般状态 | 内存不足时终止 |
3 | 弱引用 | GC 时 | 对象缓存 | GC 后终止 |
4 | 虚引用 | unknow | unknow | unknow |
参考资料
https://www.zhihu.com/question/37401125
http://www.importnew.com/23182.html
https://zh.wikipedia.org/wiki/%E5%BC%B1%E5%BC%95%E7%94%A8
https://www.ibm.com/developerworks/cn/java/j-jtp11225/index.html
https://www.cnblogs.com/dolphin0520/p/3784171.html
https://blog.csdn.net/mazhimazh/article/details/19752475