Java 中对象的引用分为四种,可以让我们更好的保证程序运行时足够的内存,这也是面试时经常问到的题目,在此记录一下。
一、强引用
最开始学习的 Java 变量的声明方式其实就是强引用,这是最常用、最普遍的引用。
String str = new String("Hello World");
这其实就是强引用。如果一个对象具有强引用,GC 绝不会回收它。当内存不够用时,JVM 宁愿抛出 OOM 异常也不会回收强引用对象。只有显式将强引用置为 null 才可以释放对象。
如下代码,最终会抛出异常 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space(自己测试时为了更快看到效果可以将 JVM 堆内存改小一下,如何设置可自行百度)。
package com.qinshou.resume.reference;
import java.util.ArrayList;
import java.util.List;
public class ReferenceDemo {
private static boolean sStart = true;
public static void main(String[] args) {
strongReference();
}
public static void strongReference() {
String str = new String("Hello World");
List<String> list = new ArrayList<>();
int index = 0;
long startTime = System.currentTimeMillis();
while (sStart) {
list.add(new String("Hello World " + (index++)));
System.out.println("执行时间--->" + (System.currentTimeMillis() - startTime));
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
二、软引用
对于软引用,如果内存空间足够,GC 就不会回收它,如果内存不足了才会回收。
Java 中所有的引用都是 Reference 的子类,它是个抽象类,软引用对应的实现类是 SoftReference。我们可以将一个对象作为参数来创建 SoftReference 对象,这样就可以将这个对象的引用指定为软引用了。然后可以通过 SoftReference 对象的 get() 方法来获取传入的对象。
String str = new String("Hello World");
Reference<String> softReference = new SoftReference<>(str);
System.out.println("softReference.get--->" + softReference.get());
软引用、弱引用、虚引用都可以搭配 ReferenceQueue 来使用,当所引用的对象被 GC 回收时,虚拟机会把这个引用加入到这个引用队列中。我们可以利用这个 ReferenceQueue 来跟踪对象的生命周期,可以通过 poll() 或者 remove() 方法来获取被回收的引用,这两个方法的区别是一个阻塞,一个非阻塞。因为从队列中获取到的引用是被回收的引用,所以调用它的 get() 方法得到的永远是 null。
package com.qinshou.resume.reference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
public class ReferenceDemo {
private static boolean sStart = true;
public static void main(String[] args) {
softReference();
}
public static void softReference() {
String str = new String("Hello World");
Reference<String> softReference = new SoftReference<>(str);
System.out.println("softReference.get--->" + softReference.get());
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
List<Reference<String>> list = new ArrayList<>();
new Thread(new Runnable() {
@Override
public void run() {
try {
// poll() 方法会从队列中取去第一个引用,没有的话直接返回 null
Reference<? extends String> reference = referenceQueue.poll();
System.out.println("reference--->" + reference);
// remove() 方法会从队列中取去第一个引用,没有的话会阻塞当前线程,直到有被回收的引用
reference = referenceQueue.remove();
System.out.println("reference--->" + reference);
sStart = false;
// 从队列中获取的引用,调用 get() 方法获取真实对象的时候永远为 null,因为已经被回收掉了
Object object = reference.get();
System.out.println("object--->" + object);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
int index = 0;
long startTime = System.currentTimeMillis();
while (sStart) {
list.add(new SoftReference<>(new String("Hello World " + (index++)), referenceQueue));
System.out.println("执行时间--->" + (System.currentTimeMillis() - startTime));
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
三、弱引用
弱引用跟软引用的区别的在于,软引用只会在内存空间不足时 GC 才会回收,而弱引用的话只要 GC 扫描到该引用所在内存区域,无论内存空间是否充足都会回收。
package com.qinshou.resume.reference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class ReferenceDemo {
private static boolean sStart = true;
public static void main(String[] args) {
weakReference();
}
public static void weakReference() {
String str = new String("Hello World");
Reference<String> weakReference = new WeakReference<>(str);
System.out.println("weakReference.get--->" + weakReference.get());
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
List<Reference<String>> list = new ArrayList<>();
new Thread(new Runnable() {
@Override
public void run() {
try {
Reference<? extends String> reference = referenceQueue.remove();
sStart = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
int index = 0;
long startTime = System.currentTimeMillis();
while (sStart) {
list.add(new WeakReference<>(new String("Hello World " + (index++)), referenceQueue));
System.out.println("执行时间--->" + (System.currentTimeMillis() - startTime));
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
上述代码的 while 循环,退出得会比使用软引用时要快很多。在 Android 中,使用弱引用是防止内存泄漏的一个常见手段。
四、虚引用
虚引用又称幽灵引用、影子引用,它无法通过 get 方法获取到实例,如果一个对象仅持有虚引用,那它跟没有引用一样。虚引用主要跟 ReferenceQueue 一起搭配使用,用来跟踪对象被 GC 回收。
package com.qinshou.resume.reference;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
public class ReferenceDemo {
private static boolean sStart = true;
public static void main(String[] args) {
phantomReference();
}
public static void phantomReference() {
String str = new String("Hello World");
PhantomReference<String> phantomReference = new PhantomReference<>(str, null);
System.out.println("phantomReference.get--->" + phantomReference.get());
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
List<Reference<String>> list = new ArrayList<>();
new Thread(new Runnable() {
@Override
public void run() {
try {
Reference<? extends String> reference = referenceQueue.remove();
sStart = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
int index = 0;
long startTime = System.currentTimeMillis();
while (sStart) {
list.add(new PhantomReference<>(new String("Hello World " + (index++)), referenceQueue));
System.out.println("执行时间--->" + (System.currentTimeMillis() - startTime));
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
五、总结
合理使用 Java 提供的各种引用,可以更好的控制程序内存,优化我们的程序,减少 OOM 的风险。比如在设计一些缓存机制的时候,将缓存对象设置为软引用或虚引用,既可以实现高速缓存,又能避免一些内存问题。