自己说:以前学习强软弱虚引用的时候,只是走马观花看看博客和视频,并没有自己写代码去实践和证明,没内化掌握,每次看完后,过不了多久就忘了。也没有用到日常开发中。这篇就整理下自己的理解。
一、前言
就像在日常生活中,从超市购买了某样物品后,如果有用,就一直保留它,否则就把它扔到垃圾箱里让清洁工人收走。一般说来,如果物品已经被扔到垃圾箱,就不可能再把它捡回来使用了。
但有时候情况并不这么简单,我们可能会遇到一些鸡肋的物品,食之无味,弃之可惜。这种物品目前已经无用了,保留它会占空间,但是立刻扔掉它也不划算,因为可能未来还会派用场。对于这样的可有可无的物品,一种折中办法是:如果家里空间足够,就先把它保留在家里;如果家里空间不够(即使把家里所有的垃圾清除,还是无法容纳那些必不可少的生活用品),那么再扔掉这些可有可无的物品。
二、发展
- 在 JDK 1.2 以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于(reachable)可达状态,程序才能使用它。
- 从 JDK 1.2 版本开始,对象的引用被划分为 4 种级别:由高到低依次为:强引用 > 软引用 > 弱引用 > 虚引用。
三、概念详解
1.强引用(Strong Reference)
- 不要胡乱持有着不放,不然内存泄露、oom有你好看。
- 就像是老板(OOM)的亲儿子一样,在公司可以什么事都不干,但是千万不要老是占用公司的资源为他自己做事,记得用完之后,要让她们去工作(资源要懂得释放) 不然公司很可能会垮掉的。
- 使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
- 当内存空间不足时,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
- 是内存泄漏的主要原因,常见场景:静态集合类持有大量对象的强引用;数据库连接、文件流、网络连接等资源在使用完毕后没有正确关闭;非静态内部类会隐式地持有对外部类的引用;事件监听器或回调函数,但忘记在不再需要时注销;ThreadLocal 未清理
Java
// 创建对象,强引用
Object strongReference = new Object();
// 如果强引用对象不使用时,需要弱化从而使 GC 能够回收
// 显式地设置 strongReference 对象为 null,
// 或让其超出对象的生命周期范围,则 GC 认为该对象不存在引用
strongReference = null;
强引用示例:
java
class Student {
//Java Object finalize() 方法
// 用于实例被垃圾回收器回收的时触发的操作 JVM不保证此方法总被调用
// 当然,在实际开发中,千万不要重写finalize方法,原因:执行时机不确定、性能问题、可能导致死锁等
@Override
protected void finalize() throws Throwable {
System.out.println("Student 被回收了");
}
}
public class Solution {
public static void main(String[] args) {
//强引用
Student student = new Student();
//中断强引用
student = null;
//手动调用GC
System.gc();
}
}
2.软引用(SoftReference)
- 描述一些还有用,但并非必需的对象。如果一个对象只具有软引用,那就类似于可有可无的生活用品。
- 有点像老板(OOM)的亲戚,在公司收益不好有可能会被开除,即使你投诉他(调用GC)上班看p,但是只要不被老板看到(被JVM检测到)就不会被开除(被虚拟机回收)
- 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存,但是system.gc对其无效
- 只要垃圾回收器没有回收它,该对象就可以被程序使用
- 软引用可用来实现内存敏感的高速缓存。
- 软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,JVM就会把此对象加入到关联的引用队列中。
- 当内存不足时,JVM 首先将软引用中的对象引用置为 null,然后通知垃圾回收器进行回收
- 软引用 本身通常不会直接造成内存泄漏,但使用不当会导致间接的内存泄漏。常见的内存泄漏场景是: 没有正确处理 引用队列,或者持有了 软引用 对象的强引用。
java
if(JVM内存不足) {
// 将软引用中的对象引用置为null
obj = null;
// 通知垃圾回收器进行回收
System.gc();
}
// 垃圾收集线程会在虚拟机抛出 OutOfMemoryError 之前回收软引用对象,
// 而且虚拟机会尽可能优先回收长时间闲置不用的软引用对象。
// 对那些刚构建的或刚使用过的 "较新的"软对象会被虚拟机尽可能保留,这就是引入引用队列 ReferenceQueue 的原因
软引用回收示例
java
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
System.out.println(softReference.get());
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(softReference.get());
// 运行后:
[B@11d7fff
[B@11d7fff
null
// 手动完成 GC 后,软引用对象包裹的 byte[] 还活的好好的,
// 但是当我们再创建了一个 10M 的 byte[] 后,最大堆内存不够了,
// 所以把软引用对象包裹的 byte[] 给干掉了。如果不干掉,就会抛出OOM。
3.弱引用(WeakReference)
- 就是一个普通的员工,平常如果表现不佳会被开除(对象没有其他引用的情况下),遇到别人投诉(调用GC)上班看p,那开除是肯定了(被虚拟机回收)。
- 在GC线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它。
- 如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,应该用 WeakReference 来记住此对象。
- 同样的,弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,JVM就会把它用加入到关联的引用队列中。
- 弱引用本身不会直接造成内存泄漏,原因:允许立即回收,不会阻止回收。相关的内存泄漏(间接泄漏):没有正确处理 引用队列,或者持有了 弱引用 对象的强引用;弱哈希映射 的键对象如果生命周期过长,也会导致内存占用。
- 弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。
java
//创建一个弱引用
WeakReference<Student> studentWeakReference
= new WeakReference<Student>(new Student());
// 从弱引用对象get获得被包裹的对象是弱引用
System.out.println(studentWeakReference.get().isFighting)
//调用get方法用一个obj接,此时student是一个强引用
Student student = studentWeakReference.get();
弱引用示例:
java
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
// 运行:
[B@11d7fff
null //gc后被回收
4.虚引用(PhantomReference)
- 顾名思义,就是形同虚设。
- 这货估计就是公司的一个临时工或实习生,遇到事情的时候想到了你,没有事情的时候,秒秒钟拿出去顶锅,开除。
- 虚引用不会决定对象的生命周期,如果一个对象仅持有虚引用,其实就和没有一样。在任何时候都可能被GC回收
- 无法通过虚引用来获取对一个对象的真实引用,主要用于跟踪对象的回收和释放外部资源。
- 虚引用与软引用和弱引用的一个区别在于,虚引用必须和引用队列(ReferenceQueue)联合使用。
java
public class Solution {
public static void main(String[] args) {
//引用队列
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
System.out.println(reference.get());
}
}
// 运行后:
null
// 虚引用的 get() 源码
public T get() {
return null;
}
虚引用实践:
java
class Student {
//Java Object finalize() 方法
// 用于实例被垃圾回收器回收的时触发的操作 JVM不保证此方法总被调用
// 当然,在实际开发中,千万不要重写finalize方法,原因:执行时机不确定、性能问题、可能导致死锁等
@Override
protected void finalize() throws Throwable {
System.out.println("Student 被回收了");
}
}
public class Solution {
public static void main(String[] args) {
ReferenceQueue queue = new ReferenceQueue();
List<byte[]> bytes = new ArrayList<>();
PhantomReference<Student> reference = new PhantomReference<Student>(new Student(),queue);
// 线程一
new Thread(() -> {
for (int i = 0; i < 100;i++ ) {
bytes.add(new byte[1024 * 1024]);
}
}).start();
// 线程二
new Thread(() -> {
while (true) {
Reference poll = queue.poll();
if (poll != null) {
System.out.println("虚引用被回收了:" + poll);
}
}
}).start();
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
}
}}
// 运行后:
Student 被回收了
虚引用被回收了:java.lang.ref.PhantomReference@1ade6f1
// 第一个线程往集合里面塞数据,随着数据越来越多,肯定会发生GC。
// 第二个线程死循环,从queue里面拿数据,如果拿出来的数据不是null,就打印出来。
// 可以看到:当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。
四、可达性
- 强可达(Strongly Reachable),就是当一个对象可以有一个或多个线程可以不通过各种引用访问到的情况。比如,新创建一个对象,那么创建它的线程对它就是强可达。
- 软可达(Softly Reachable),就是当我们只能通过软引用才能访问到对象的状态。
- 弱可达(Weakly Reachable),类似前面提到的,就是无法通过强引用或者软引用访问,只能通过弱引用访问时的状态。这是十分临近fnalize状态的时机,当弱引用被清除的时候,就符合finalize的条件了。
- 幻象可达(Phantom Reachable),就是没有强、软、弱引用关联,并且finalize过了,只有幻象引用指向这个对象的时候。当然,还有一个最后的状态,就是不可达(unreachable),意味着对象可以被清除了。
五、总结
引用类型 | GC回收时间 | 用途 | 生存时间 |
---|---|---|---|
强引用 | never | 对象的一半状态 | JVM停止运行 |
软引用 | 内存不足时 | 对象缓存 | 内存不足时终止 |
弱引用 | GC时 | 对象缓存 | GC后终止 |
虚引用 | unknow | unknow | unknow |
That's all~~~