004.强引用、软引用、弱引用、幻象引用有什么区别!
JVM 垃圾回收中,GC判断堆中的对象实例或数据是不是垃圾的方法有引用计数法和可达性算法两种。
无论是通过引用计数算法判断对象的引用数量,还是通过根搜索算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。
Java中的引用
我们知道 Java 数据类型分两大类,基本类型和引用类型。
基本类型:
- 4种整数类型:byte、short、int、long
- 2种浮点数类型:float、double
- 1种字符类型:char
- 1种布尔类型:boolean
引用类型:引用类型指向一个对象,不是原始值,指向对象的变量是引用变量。
- 类、接口、数组、枚举、注解
在 JDK 1.2 之前,Java 中的引用的定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该 refrence 数据是代表某块内存、某个对象的引用。
在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为
- 强引用(Strong Reference)
- 软引用(Soft Reference)
- 弱引用(Weak Reference)
- 虚引用(Phantom Reference)
这四种引用强度依次逐渐减弱。
Java 中引入四种引用的目的是让程序自己决定对象的生命周期。
强引用
在 Java 中最常见引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。类似 “Object obj = new Object()”
这类的引用。
当一个对象被强引用变量引用时,它处于可达状态,即使不被用到或者发生OOM也不会被垃圾回收器回收的。
对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为 null,一般认为就是可以被垃圾收集器回收。
public class StrongRefenenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = o1;
o1 = null;
System.gc();
System.out.println(o1); //null
System.out.println(o2); //java.lang.Object@2503dbd3
}
}
demo 中尽管 o1已经被回收,但是 o2 强引用 o1,一直存在,所以不会被GC回收。
软引用
软引用需要用java.lang.ref.SoftReference
类来实现,可以让对象豁免一些垃圾收集。
软引用用来描述一些还有用,但并非必需的对象。
当系统内存充足时它不会被回收,当系统内存不足时它才会被回收。
//VM options: -Xms5m -Xmx5m
public class SoftRefenenceDemo {
public static void main(String[] args) {
softRefMemoryEnough();
System.out.println("------内存不够用的情况------");
softRefMemoryNotEnough();
}
private static void softRefMemoryEnough() {
Object o1 = new Object();
SoftReference<Object> s1 = new SoftReference<Object>(o1);
System.out.println(o1);
System.out.println(s1.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(s1.get());
}
/**
* JVM配置`-Xms5m -Xmx5m` ,然后故意new一个一个大对象,使内存不足产生 OOM,看软引用回收情况
*/
private static void softRefMemoryNotEnough() {
Object o1 = new Object();
SoftReference<Object> s1 = new SoftReference<Object>(o1);
System.out.println(o1);
System.out.println(s1.get());
o1 = null;
byte[] bytes = new byte[10 * 1024 * 1024];
System.out.println(o1);
System.out.println(s1.get());
}
}
java.lang.Object@2503dbd3
java.lang.Object@2503dbd3
null
java.lang.Object@2503dbd3
------内存不够用的情况------
java.lang.Object@4b67cf4d
java.lang.Object@4b67cf4d
java.lang.OutOfMemoryError: Java heap space
at reference.SoftRefenenceDemo.softRefMemoryNotEnough(SoftRefenenceDemo.java:42)
at reference.SoftRefenenceDemo.main(SoftRefenenceDemo.java:15)
null
null
软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收。如 Mybatis 缓存类。
弱引用
弱引用也是用来描述非必需对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。
当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
弱引用需要用java.lang.ref.WeakReference
类来实现,它比软引用的生存期更短。
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> w1 = new WeakReference<Object>(o1);
System.out.println(o1);
System.out.println(w1.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(w1.get());
}
}
我们看下 ThreadLocal (线程本地变量) 中用到的弱引用
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//......
}
虚引用
虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。
虚引用需要java.lang.ref.PhantomReference
来实现。
虚引用必须和引用队列(RefenenceQueue)联合使用。
虚引用的主要作用是跟踪对象垃圾回收的状态。仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。
PhantomReference 的 get 方法总是返回 null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入 finalization 阶段,可以被 GC 回收,用来实现比 finalization 机制更灵活的回收操作。
换句话说,设置虚引用的唯一目的,就是在这个对象被回收器回收的时候收到一个系统通知或者后续添加进一步的处理。
Java 允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
引用队列
ReferenceQueue 是用来配合引用工作的,没有ReferenceQueue 一样可以运行。
SoftReference、WeakReference、PhantomReference 都有一个可以传递 ReferenceQueue 的构造器。
创建引用的时候,可以指定关联的队列,当 GC 释放对象内存的时候,会将引用加入到引用队列。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动,这相当于是一种通知机制。
当关联的引用队列中有数据的时候,意味着指向的堆内存中的对象被回收。通过这种方式,JVM 允许我们在对象被销毁后,做一些我们自己想做的事情。