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
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
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
softReference = new SoftReference<>(str); - System.out.println("softReference.get--->" + softReference.get());
- ReferenceQueue
referenceQueue = new ReferenceQueue<>(); - List
> 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
weakReference = new WeakReference<>(str); - System.out.println("weakReference.get--->" + weakReference.get());
- ReferenceQueue
referenceQueue = new ReferenceQueue<>(); - List
> 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
phantomReference = new PhantomReference<>(str, null); - System.out.println("phantomReference.get--->" + phantomReference.get());
- ReferenceQueue
referenceQueue = new ReferenceQueue<>(); - List
> 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 的风险。比如在设计一些缓存机制的时候,将缓存对象设置为软引用或虚引用,既可以实现高速缓存,又能避免一些内存问题。