对于大部分对象而言,程序里会有一个引用变量引用该对象,例如 Object o = new Object();这种引用方式就是常见的引用方式,强引用。除此之外,java.lang.ref包下提供了三个类: SoftReference,WeakReference和PhantomReference,它们分别代表系统对对象的三种引用方式:软引用,弱引用和虚引用。因此,Java对对象的引用方式有如下四种。
-
强引用(StrongReference)
这是Java程序中最常见的引用方式,程序创建一个对象,并把这个对象赋给一个引用变量。程序通过该引用变量来操作实际的对象。当一个对象被一个或一个以上的引用变量所引用的时候,它就处于激活状态,不可能被垃圾回收机制回收。当一个对象没有被任何一个引用变量所引用的时候,该对象就会标记为垃圾对象,被垃圾回收机制回收掉。
-
软引用(SoftReference)
软引用需要通过SoftReference类来实现。当一个对象只具有软引用的时候,在堆内存充裕的时候,是不会被垃圾回收机制回收的。只有堆内存不足的时候,垃圾回收机制将会回收它。代码如下:
运行的时候记得加一下JVM启动参数:-Xmx10M 调小堆内存。
public class SoftReferenceTest {
static volatile boolean runFlag = true;
public static void main(String[] args) throws InterruptedException {
//new 一个String对象
String str = new String("Hello,everyone");//①
//创建一个软引用指向到字符串"Hello,everyony"
SoftReference<String> sr = new SoftReference<>(str);//②
//切断str 与 "Hello,everyony" 的关联
str = null;//③
//启动一个线程,不停的消耗内存
Thread t = new Thread(new Runnable() {
List<byte[]> eatMemList = new LinkedList<>();
@Override
public void run() {
while (true){
try {
TimeUnit.SECONDS.sleep(1);
eatMemList.add(new byte[1024 * 1024]);
} catch (InterruptedException e) {
e.printStackTrace();
}catch (OutOfMemoryError error){
System.out.println("内存不足了..");
runFlag = false;
break;
}
}
}
});
t.start();
while (runFlag){
//强制执行GC
System.gc();
System.runFinalization();
//通过软引用获取字符串,null 表示已经别被回收
String s = sr.get();//④
if(null == s){
System.out.println("字符串被垃圾回收器回收掉了");
}else {
System.out.println(s);
}
}
}
}
运行结果如下:
可以看到,当内存不足后,软引用引用的对象别垃圾收集器回收掉了。大概步骤如下:
在①处创建字符串对象指向str。
在②处创建软引用对象sr引用str。
在③断开str与字符串"Hello,everyone"的强引用。
在④通过软引用对象获取字符串"Hello,everyone"。
系统内存如下图所示。
-
弱引用
弱引用通过WeakReference类实现。弱引用和软引用很像,但是弱引用的引用级别更低。对于只有弱引用引用的对象,当系统进行垃圾回收的时候,不管系统内存是否足够,总会回收该对象。当然,并不是说一个对象只有弱引用的时候,它就会立即被回收,正如那些失去引用的对象一样,必须等到系统垃圾回收器运行的时候才会被回收掉。
public class WeakReferenceTest {
public static void main(String[] args) throws InterruptedException {
//new 一个String对象
String str = new String("Hello,everyone");//①
//创建一个弱引用指向到字符串"Hello,everyony"
WeakReference<String> wr = new WeakReference<>(str);//②
//切断str 与 "Hello,everyony" 的关联
str = null;//③
System.out.println("GC前:" + wr.get());
//强制执行GC
System.gc();
System.runFinalization();
//因为GC的不确定性,所以GC后睡眠一会
TimeUnit.SECONDS.sleep(5);
System.out.println("GC前:" + wr.get());
}
}
运行结果如下:
可以看到GC后弱引用引用的对象会回收掉了。
-
虚引用
虚引用通过PhantomReference类实现,虚引用完全类似于完全没有引用。虚引用对对象本身没有太大的影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那它和没有引用的效果大致相同。虚引用主要用于跟踪一个对象被垃圾回收的状态,虚引用一般不能单独使用,一般配合引用队列(ReferenceQueue)联合使用。虚引用无法获取它所引用的对象。
Demo 如下:
public class PhantomReferenceTest {
public static void main(String[] args) throws InterruptedException {
//new 一个String对象
String str = new String("Hello,everyone");
//创建一个引用队列
ReferenceQueue<String> rq = new ReferenceQueue<>();
//创建一个虚引用指向到字符串"Hello,everyony"
PhantomReference pr = new PhantomReference(str, rq);
//不能通过虚引用访问所引用对象,所以会打印null
System.out.println("通过虚引用获取对象为:" + pr.get());//①
//切断str 与 "Hello,everyony" 的关联
str = null;
//强制执行GC
System.gc();
System.runFinalization();
//因为GC的不确定性,所以GC后睡眠一会
TimeUnit.SECONDS.sleep(5);
//取出引用队列中最先放入队列中的PhantomReference引用与pr对比
Reference<? extends String> poll = rq.poll();
System.out.println("" + (poll == pr));//②
}
}
运行结果如下:
因为无法通过虚引用来获取被引用的对象,所以①处代码的输出null(即使此时为字符串对象还为被回收,仍有强引用str指向该字符串对象)。当字符串对象"Hello,everyone"被回收后,与该对象关联的虚引用对象pr将被添加到引用队列rq中,因此将在②代码处看到输出true。
最后,不管是软引用,弱引用,还是虚引用当有强引用指向对象的的时候,该对象都不会被垃圾回收器回收掉。
参考:《疯狂JAVA讲义》