在JDK1.2之前,对象的引用情况只有:引用和未引用。从JDK1.2开始,引用状态分成了四种,从而更细粒度的控制对象的生命周期。四种引用由强到弱分别是:强引用、软引用、弱引用、虚引用。
强引用简单点说就是平时使用最多的以一种引用,一般是使用new
关键字实例化的引用:
Object object = new Object()
强引用的特点是:虚拟机宁愿抛出java.lang.OutOfMemoryError
异常也不会回收强引用指向的实例。只有虚拟机关闭/终止了,强引用才会消失。如果想要打破这种强引用的关联,可以手动把引用设置为null
object = null
这样仅仅只是打破了强引用的关联关系,对象什么时候被回收,还得取决于虚拟机。如果对GC不太了解,可以参考我之前的博客:Java虚拟机——垃圾收集算法
软引用的强度比强引用弱,用来引用一种可有可无的对象,如果内存充足,gc便不会回收软引用所引用的对象。只有内存不足时,才会回收。软引用可以用来实现缓存。软引用通过java.lang.ref.SoftReference
来实现,获取一个软引用指向的实例,使用SoftReference.get()
方法
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns null
.
* 返回此引用(指向)的引用对象,如果引用对象被程序或者GC清理,则返回null
*/
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
根据JDK文档描述,可以使用SoftReference.get()
来判断软引用指向的对象是否被回收(以下实例在运行时别忘了添加虚拟机参数,可能有同学不知道怎么添加虚拟机参数,所以我把添加参数的方法写在的文章的末尾):
/**
* 必须参数:-Xms5m -Xmx5m
* 可选参数:-XX:+PrintGCDetails
*/
public void softReferenceTest() {
SoftReference soft = new SoftReference(new byte[1024 * 1000]);
// 此时内存空间充足,不会清理SoftReference指向的对象
System.out.println(soft.get());
byte[] bytes = new byte[1024 * 1024 * 3];
// 通知GC执行垃圾回收
System.gc();
// 此时内存空间不足,清理SoftReference指向的对象
System.out.println(soft.get());
}
输出结果
[B@372f7a8d
null
根据运行结果,可以看到SoftReference所引用的对象,在内存不足时,会被回收。
System.gc()
通知虚拟机进行垃圾回收,但是虚拟机不一定会执行回收程序。可以添加虚拟参数-XX:+PrintGCDetails
打印GC日志,这样就可以看到是否进行了GC。
除了使用SoftReference.get()
方法,根据返回值是否为null来判断对象清理状态外,还可以通过结合java.lang.ref.ReferenceQueue
来判断
/**
* 必须参数:-Xms5m -Xmx5m
* 可选参数:-XX:+PrintGCDetails
*/
public void softReferenceQueueTest() {
ReferenceQueue queue = new ReferenceQueue<>();
SoftReference soft = new SoftReference(new byte[1024 * 1000], queue);
// 软引用指向的实例没有被回收,因此不会被加入到ReferenceQueue,所以输出null
System.out.println(queue.poll());
byte[] bytes = new byte[1024 * 1024 * 3];
// 通知GC执行垃圾回收
System.gc();
// 软引用指向的实例已经被回收了,会被加入到ReferenceQueue
System.out.println(queue.poll());
}
输出结果
null
java.lang.ref.SoftReference@372f7a8d
弱引用比软引用(Soft Reference)的强度更弱,也是用来引用一种可有可无的对象。和软引用不同的是,弱引用指向的对象不会等到内存不足时才会回收,而是每次GC都会回收。弱引用通过java.lang.ref.WeakReference
来实现,同样利用WeakReference.get()
方法来获取弱引用指向的实例,java.lang.ref.WeakReference
并没有重写父类java.lang.ref.Reference
中的get()
方法(其实仔细看下Soft Reference中的get()
方法也是调用父类的这个get()
方法)
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns null
.
* 返回此引用(指向)的引用对象,如果引用对象被程序或者GC清理,则返回null
*/
public T get() {
return this.referent;
}
同样的,我们可以利用get()
方法来判断弱引用指向的实例是否被回收
/**
* 必须参数:-Xms5m -Xmx5m
* 可选参数:-XX:+PrintGCDetails
*/
public void weakReferenceTest() {
WeakReference weak = new WeakReference<>(new byte[1024 * 1000]);
System.out.println(weak.get());
// 通知GC执行垃圾回收
System.gc();
// 此时内存空间充足,对象依然被回收了
System.out.println(weak.get());
}
输出结果
[B@51887dd5
null
和SoftReference
一样,还可以通过结合java.lang.ref.ReferenceQueue
来判断引用指向的实例是否被回收,代码基本和软引用一致,就不赘述了。
虚引用的强度比弱引用(Weak Reference)更弱,虚引用指向的实例可以说完完全全是无用对象。回收时间不确定,甚至无法被获取,作用仅仅是在被回收的时候收到一个系统通知。根据软引用和弱引用的经验,我们可以试试用get()
获取虚引用指向的实例,但是虚引用的get()
方法定义如下:
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* null
.
* 返回此引用的对象指向的实例,因为虚引用指向的实例总是不可达(无法访问),所以该方法总是返回null
*/
public T get() {
return null;
}
这也正好印证了我之前说的,虚引用指向的实例无法被获取,所以只能结合java.lang.ref.ReferenceQueue
一起使用,这也是虚引用和软引用、弱引用不同的地方之一。
public void phantomReferenceQueueTest() {
ReferenceQueue queue = new ReferenceQueue<>();
PhantomReference phantom = new PhantomReference<>(new byte[1024 * 1000], queue);
System.out.println(queue.poll());
// 通知GC执行垃圾回收
System.gc();
System.out.println(queue.poll());
}
多运行几次,结果总是不确定的,所以并不是GC之后,虚引用指向的实例就一定会被回收。
回收时间 | 作用 | 生命周期 | |
---|---|---|---|
强引用 | 不会被回收 | 普通对象 | 虚拟机关闭 |
软引用 | 内存不足时 | 高速缓存 | 内存不足时,GC之后 |
弱引用 | GC时 | 高速缓存 | GC之后 |
虚引用 | - | 哨兵 | - |
如何添加虚拟机参数(以IDEA为例)
1、IDEA右上角,点击Edit Configurations
2、选择要运行的项目/主类,在VM options中填入虚拟机参数,点击右下角Apply,再启动就行了。