三角引用的对象
废话不多说, 直接实践一下, 三角引用的对象能否被 GC 回收呢?
public class MainActivity extends AppCompatActivity {
A a = new A();
A1 a1 = new A1();
A2 a2 = new A2();
@Override
protected void onCreate(Bundle savedInstanceState) {
...
a.a1 = a1;
a1.a2 = a2;
a2.a = a;
a = null;
a1 = null;
a2 = null;
}
手动执行GC前, A, A1, A2 是存活的
手动执行GC后, 我们发现,相互引用的对象 A, A1, A2 消失了
意味着虚拟机并没有因为这三个对象互相引用就不回收它们,这也从侧面说明虚拟机并不是通过引用计数算法来判断对象是否存活的。 同理两个相互引用的对象.
其实虚拟机使用的是 “可达性算法(根搜索算法)”
可达性算法
原理:通过一系列的称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
图中,对象object 5、object 6、object 7虽然互相有关联,但是它们到GC Roots是不可达的,所以它们将会被判定为是可回收的对象。
在Java语言中,可作为GC Roots的对象包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的Native方法)引用的对象
在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱
强引用
强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下:
A a = new A();
当内存空间不足,虚拟机会抛出 OutOfMemoryError 错误,使程序异常终止,而不是随意回收具有强引用的对象来解决内存不足的问题。如果强引用的对象不使用时,要通过如下方式来弱化引用,如下:
a = null; // 帮助垃圾收集器回收此对象
显式地设置 a 为null,或超出对象的生命周期范围,则 gc 认为该对象不可达,这时就可以回收这个对象。
软引用
用来描述一些还有用,但并非必需的对象。
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
String str = new String("abc"); // 强引用
SoftReference<String> softRef = new SoftReference<String>(str); // 软引用
当内存不足时,等价于:
If(JVM.内存不足()) {
str = null; // 转换为软引用
System.gc(); // 垃圾回收器进行回收
}
软引用在实际中有重要的应用,例如抖音的上滑下滑播放视频。当上滑下滑时播放的视频是重新进行请求还是从缓存中取出呢?
- 如果视频在浏览结束时(上滑下滑)就进行内容的回收,则下滑查看上一个视频时,需要重新构建
- 如果将浏览过的视频存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用。
Video prev = new Video(); // 获取视频进行浏览
SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用
if(sr.get() != null) {
rev = (Video) sr.get(); // 还没有被回收器回收,直接获取
} else {
prev = new Video(); // 由于内存吃紧,所以对软引用的对象回收了
sr = new SoftReference(prev); // 重新构建
}
这样就很好的解决了实际的问题。 当然了, 以上并不是抖音真实的做法, 只是举个例子.
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用
也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
String str = new String("weakRef");
WeakReference weakReference = new WeakReference<String>(str);
str = null;
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。
下面的代码会让str再次变为一个强引用:
String str2 = weakReference.get();
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。
虚引用
“虚引用”顾名思义,就是形同虚设,也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。
在JDK 1.2之后,提供了PhantomReference类来实现虚引用。
虚引用,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
我们可以声明虚引用来引用我们感兴趣的对象,在GC要回收的时候,GC收集器会把这个对象添加到ReferenceQueue,这样我们如果检测到ReferenceQueue中有我们感兴趣的对象的时候,说明GC将要回收这个对象了。此时我们可以在GC回收之前做一些其他事情,比如记录些日志什么的。
简单图表表示他们的关系
额外的,
注意:任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,因此第二段代码的自救行动失败了,并且建议大家尽量避免使用它 。