1. 强引用
Java中的引用,类似于C++的指针。通过引用,可以对堆中的对象进行操作。在某个函数中,当创建了一个对象,该对象被分配在堆中,通过这个对象的引用才能对这个对象进行操作。
StringBuffer str = new StringBuffer("hello world");
假设以上代码是在函数体内运行的,那么局部变量str将被分配在栈上,而对象StringBuffer实例,被分配在堆上。局部变量str指向StringBuffer实例所在的堆空间,通过str可以操作该实例,那么str就是StringBuffer的引用。
此时,运行一个赋值语句:
StringBuffer str1 = str;
那么,str所指向的对象也将被str1所指向,同时在局部栈空间上会分配空间存放str1变量。此时,该StringBuffer实例就有两个引用。对引用的"=="操作用于表示两个操作数所指向的堆空间地址是否相同,不表示两个操作数所指向的对象是否相等。
强引用特点:
强引用可以直接访问目标对象。
强引用所指向的对象在任何时候都不会被系统回收。JVM宁愿抛出OOM异常,也不会回收强引用所指向的对象。
强引用可能导致内存泄露。
2. 软引用
软引用是除了强引用外,最强的引用类型。可以通过java.lang.ref.SoftReference使用软引用。一个持有软引用的对象,不会被JVM很快回收,JVM会根据当前堆的使用情况来判断何时回收。当堆的使用率临近阈值时,才会回收软引用的对象。
package com.qunar.base;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
/**
* Created by xiaosi on 16-3-24.
*/
public class ReferenceDemo {
// 创建引用队列
private ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
public class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
// 被回收时输出
System.out.println("MyObject is finalize called");
}
@Override
public String toString() {
return " I am MyObject";
}
}
public class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
// 如果对象被回收则进入引用队列
obj = (Reference<MyObject>) referenceQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("Object for SoftReference is " + obj.get());
}
}
}
public void test() {
// 创建强引用
MyObject myObject = new MyObject();
// 构造myObject对象的软引用 注册到 引用队列
SoftReference<MyObject> softReference = new SoftReference<>(myObject, referenceQueue);
CheckRefQueue checkRefQueue = new CheckRefQueue();
Thread thread = new Thread(checkRefQueue);
thread.start();
// 删除强引用 对myObject对象的引用只剩下软引用
myObject = null;
System.gc();
System.out.println("After GC: Soft Get = " + softReference.get());
System.out.println("分配大块内存");
// 分配一块强大的内存,强迫GC
byte[] b = new byte[5*1024*963];
System.out.println("After new byte[]:Soft Get = "+softReference.get());
System.gc();
}
public static void main(String[] args) {
ReferenceDemo referenceDemo = new ReferenceDemo();
referenceDemo.test();
}
}
首先构造MyObject对象,并将其赋值给myObject变量,构成强引用。然后使用SoftReference构造这个MyObject对象的软引用softReference,并注册到referenceQueue队列中。当softReference被回收时,会被加入referenceQueue队列。设置myObject=null,删除这个强引用,因此,系统内对MyObject对象的引用只剩下软引用。此时,显示调用GC,通过软引用的get()方法,取得MyObject对象实例的强引用,发现对象被未回收。这说明GC在内存充足的情况下,不会回收软引用对象。
接着,请求一块大的堆空间new byte[4*1024*925],这个操作会使操作系统内存使用紧张,从而产生新一轮的GC。这次GC后,softReference.get()不再返回MyObject对象,而是返回null,说明在系统内存紧张的情况下,软引用被回收。软引用被回收时,会被加入到注册的引用队列中。
备注:
JVM参数:-Xmx5M
运行结果:
/opt/jdk1.7.0_40/bin/java -Xmx5M ...
After GC: Soft Get = I am MyObject
分配大块内存
After new byte[]:Soft Get = null
MyObject is finalize called
Object for SoftReference is null
3. 弱引用
弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此,并一不定能很快的发现持有弱引用的对象。这种情况下,弱引用对象可以存在较长的一段时间。一旦一个弱引用对象被垃圾回收器回收,便会加入到一个注册引用队列中。
package com.qunar.base;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
/**
* Created by xiaosi on 16-3-24.
*/
public class WeakReferenceDemo {
// 创建引用队列
private ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
public class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
// 被回收时输出
System.out.println("MyObject is finalize called");
}
@Override
public String toString() {
return " I am MyObject";
}
}
public class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
// 如果对象被回收则进入引用队列
obj = (Reference<MyObject>) referenceQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("Object for SoftReference is " + obj.get());
}
}
}
public void test() {
// 创建强引用
MyObject myObject = new MyObject();
// 构造myObject对象的弱引用 注册到 引用队列
WeakReference<MyObject> weakReference = new WeakReference<>(myObject, referenceQueue);
CheckRefQueue checkRefQueue = new CheckRefQueue();
Thread thread = new Thread(checkRefQueue);
thread.start();
// 删除强引用 对myObject对象的引用只剩下弱引用
myObject = null;
System.out.println("Before GC: Weak Get = " + weakReference.get());
System.gc();
System.out.println("After GC: Weak Get = " + weakReference.get());
}
public static void main(String[] args) {
WeakReferenceDemo referenceDemo = new WeakReferenceDemo();
referenceDemo.test();
}
}
运行结果:
Before GC: Weak Get = I am MyObject
After GC: Weak Get = null
MyObject is finalize called
Object for SoftReference is null
在GC之前,弱引用对象并未被垃圾回收器发现,因此通过weakRef.get()方法可以取得对应的强引用。但是只要进行垃圾回收,弱引用对象一旦被发现,便会立即被回收,并加入注册引用队列中。此时,再次通过weakRef.get()方法取得强引用就会失败。
备注:
软引用,弱引用都非常适合来保存那些可有可无的缓存数据。如果这样做,当系统内存不足时,这些缓存数据会被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间。
4. 虚引用
虚引用是所有引用类型中最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。当试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,销毁这个对象,奖这个虚引用加入引用队列。
package com.qunar.base;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
/**
* Created by xiaosi on 16-3-24.
*/
public class PhantomReferenceDemo {
// 创建引用队列
private ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
public class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
// 被回收时输出
System.out.println("MyObject is finalize called");
}
@Override
public String toString() {
return " I am MyObject";
}
}
public class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
// 如果对象被回收则进入引用队列
obj = (Reference<MyObject>) referenceQueue.remove();
// 等待 直到取得虚引用对象
System.out.println("Object for PhantomReference is " + obj.get());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("Object for SoftReference is " + obj.get());
}
}
}
public void test() throws InterruptedException {
// 创建强引用
MyObject myObject = new MyObject();
// 构造myObject对象的虚引用 注册到 引用队列
PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject, referenceQueue);
System.out.println("phantomReference Get : " + phantomReference.get());
CheckRefQueue checkRefQueue = new CheckRefQueue();
Thread thread = new Thread(checkRefQueue);
thread.start();
// 删除强引用 对myObject对象的引用只剩下虚引用
myObject = null;
Thread.sleep(1000);
int i = 1;
while(true){
System.out.println("第" + i + "次GC");
System.gc();
Thread.sleep(1000);
i++;
}//while
}
public static void main(String[] args) throws InterruptedException {
PhantomReferenceDemo referenceDemo = new PhantomReferenceDemo();
referenceDemo.test();
}
}
运行结果:
phantomReference Get : null
第1次GC
MyObject is finalize called
第2次GC
Object for PhantomReference is null
从这个输出结果中可以看出,对虚引用的get()操作,总是返回null,即便强引用还存在时,也不例外。因为虚引用的get()实现:
public T get() {
return null;
}
在第一次GC时,系统找到了垃圾对象,并调用其finalize()方法回收内存,但没有立即加入到回收队列中。第二次GC时,该对象真正的被GC清除,此时,加入虚引用队列。
虚引用最大作用在于跟踪对象回收,清理被销毁对象的相关资源。通常,当对象不被使用时,重载该类的finalize()方法可以回收该对象的资源。但是,如果finalize()方法使用不慎,可能导致该对象复活。
下面看一个错误的finalize()实现的例子,这个实现导致内存溢出,或者对象永远无法被回收。
package com.qunar.base;
/**
* Created by xiaosi on 16-3-24.
*/
public class PhantomObject {
public static PhantomObject phantomObject;
@Override
protected void finalize() throws Throwable {
super.finalize();
// 被回收时输出
System.out.println("MyObject is finalize called");
// 在finalize()中拯救了将要被回收的对象
phantomObject = this;
}
@Override
public String toString() {
return " I am MyObject";
}
public static void main(String[] args) throws InterruptedException {
phantomObject = new PhantomObject();
// 删除对象
phantomObject = null;
// 第一次GC
System.gc();
Thread.sleep(1000);
if(phantomObject == null){
System.out.println("phantomObject is null");
}//if
else{
System.out.println("phantomObject 可用");
}
System.out.println("第二次GC");
System.gc();
Thread.sleep(1000);
if(phantomObject == null){
System.out.println("phantomObject is null");
}//if
else{
System.out.println("phantomObject 可用");
}
System.out.println("第三次GC");
System.gc();
Thread.sleep(1000);
if(phantomObject == null){
System.out.println("phantomObject is null");
}//if
else{
System.out.println("phantomObject 可用");
}
}
}
在上面代码中,先将对象phantomObject设置为null,告知GC这是一个需要清理的对象。然后,进行一次显示的GC,GC过后发现,虽然该对象的finalize()方法被调用,但是对象依然存在。随后,进行第二次GC,由于在GC之前没有清除对象的强引用,所以phantomObject依然没有被回收。
运行结果:
MyObject is finalize called
phantomObject 可用
第二次GC
phantomObject 可用
第三次GC
phantomObject 可用
可见,虽然finalize()被调用,但是phantomObject始终都没有被回收。如果要强制回收phantomObject,需要在第二次G前,使用phantomObject=null,去除该对象的强引用。由于finalize()只会被调用一次,因此在第二次回收时,对象就没有机会复活了。
运行结果:
MyObject is finalize called
phantomObject 可用
第二次GC
phantomObject is null
第三次GC
phantomObject is null
由于在finalize()中存在让回收对象复活的可能性,因此,在一个复杂的应用系统中,一旦finalize()方法实现有问题,就很容易造成内存泄露。而使用虚引用来清理相关资源则不会有类似的问题,因为虚引用队列中对象,事实上已经完成了对象的回收工作,是不可能再度复活该对象的。