Java|Java四种引用类型

Java 四种引用类型 引用与对象

每种编程语言都有自己操作内容中元素的形式,例如C和C++通过指针,Java中则通过"引用"。
Java中一切皆对象,但我们操作的标识符实际上是对象的一个引用"reference".
// 创建一个引用,引用可以独立存在,并不一定要与一个对象关联 String s; // 引用的标识符指向某个对象,可以通过这个引用来操作对象了。 String str = new String("xyz");

在JDK1.2之前,Java中定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,这块内存代表一个引用。一个对象只有"未引用"和"已引用"两个状态,这无法描述某些特殊情况下的对象,比如,内容充足时需要保留,而内存紧张时才需要被抛弃的一类对象。
Java中的GC回收机制在判断是否回收某个对象的时候,都需要依据这个"引用"的概念。
不同的GC算法判断引用的方式不同:
  • 引用计数法(Reference Count):为对象添加一个引用计数器,有引用指向它则计数器+1;引用失效,计数器-1;当引用计数为0时,则该对象可以被GC回收,但是该算法无法解决对象之间循环引用的问题(目前Java已放弃这种方式)。
  • 可达性分析法(GC Roots):从GC Roots的对象开始搜索,如果一个对象到GC Roots没有任何引用链接时,说明此对象不可用,可以被GC回收。同时解决了引用计数算法中的循环引用的问题。
引用类型
JDK1.2以后,Java对引用的概念进行了扩充,分为:强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference)和虚引用(Phantom Reference)四种。四种引用强度依次减弱。
1. 强引用 (Strong Reference) Java中默认使用的就是强引用
// 创建一个Object对象,且使用 o 引用指向该对象 // 只要 o 还指向Object对象,就不会被GC回收,即使内存不足时,抛OOM异常也不会被回收。 Object o = new Object(); // 制成null,下次GC就会回收 o = null;

2. 软引用(Soft Reference) 软引用是用来描述一些非必需但仍有用的对象。
内存足够时,软引用不会被回收;只有内存不足时,GC才会回收软引用的对象;如果回收后仍然没有足够内存时,才会抛OOM异常。
使用场景:缓存技术,包括JVM缓存,图片缓存等
Java中用 java.lang.ref.SoftReference 类表示软引用
软引用与强引用对比 JVM启动参数:-Xms20m -Xmx20m
强引用示例:
public class StrongReference { public static void main(String[] args) { allocateStrongReference(); }private static void allocateStrongReference() { byte[] buffer = new byte[1024 * 1024 * 10]; } }

说明:
  • buffer 为 10M时,程序运行正常
  • buffer 为 20M时,程序抛OOM异常:java.lang.OutOfMemoryError: Java heap space
软引用示例:
public class SoftReferenceTest {private static List> list = new ArrayList<>(); public static void main(String[] args) { testSoftReference(); }private static void testSoftReference() { // 创建10个大小为2M的字节数组 for (int i = 0; i < 10; i++) { byte[] buffer = new byte[1024 * 1024 * 2]; SoftReference sr = new SoftReference<>(buffer); list.add(sr); }// 手动通知GC System.gc(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }// 打印软引用对象 list.forEach(sr -> System.out.print(sr + " ")); System.out.println("----------------"); // 打印软引用中的对象 list.forEach(sr -> System.out.print(sr.get() + " ")); } }output: java.lang.ref.SoftReference@6e1b77 java.lang.ref.SoftReference@1ca772d java.lang.ref.SoftReference@1807454 java.lang.ref.SoftReference@1494fbf java.lang.ref.SoftReference@1dc4ec2 java.lang.ref.SoftReference@133314b java.lang.ref.SoftReference@11bc7ed java.lang.ref.SoftReference@d84586 java.lang.ref.SoftReference@10dae81 java.lang.ref.SoftReference@12c6ec2 java.lang.ref.SoftReference@dde6e5 java.lang.ref.SoftReference@177ecd java.lang.ref.SoftReference@80bfe8 java.lang.ref.SoftReference@a29884 java.lang.ref.SoftReference@169b07b java.lang.ref.SoftReference@c34f4d java.lang.ref.SoftReference@1a7cec2 java.lang.ref.SoftReference@1b3120a java.lang.ref.SoftReference@1539caf java.lang.ref.SoftReference@1fc0f2f ---------------- null null null null null null null null null null null null null null null null [B@1da6444 [B@9f23b4 [B@183da3f [B@18fd1ac

说明:
  • 无论创建多少个软引用对象,只有最后几个对象被保留,说明内存不足时软引用的对象会被GC回收
  • 即使有 byte[] buffer 引用指向对象, 且 buffer 是一个strong reference, 但是 SoftReference sr 指向的对象仍然被回收了,这是因为Java的编译器发现了在之后的代码中, buffer 已经没有被使用了, 所以自动进行了优化。
3. 弱引用(Weak Reference)
弱引用的引用强度比软引用更弱一点。无论内存是否足够,只要JVM进行GC,弱引用关联的对象都会被回收。
Java 中使用 java.lang.ref.WeakReference 来表示 弱引用。
示例如下:
public class WeakReferenceTest {private static List list = new ArrayList<>(); public static void main(String[] args) { testWeakReference(); }private static void testWeakReference() { for (int i = 0; i < 10; i++) { byte[] buff = new byte[1024 * 1024]; WeakReference sr = new WeakReference<>(buff); list.add(sr); }System.gc(); // 打印弱引用中的对象 list.forEach(sr -> System.out.print(sr.get() + " ")); } }output: null null null null null null null null null null

弱引用关联的对象都被GC回收了。
4. 虚引用(PhantomReference)
虚引用是最弱的一种引用关系,如果一个对象持有虚引用,那么它就和没有引用一样,它随时可能会被回收。
Java中使用 java.lang.ref.PhantomReference 类表示。
源码:
package java.lang.ref; /** * Phantom reference objects, which are enqueued after the collector * determines that their referents may otherwise be reclaimed.Phantom * references are most often used for scheduling pre-mortem cleanup actions in * a more flexible way than is possible with the Java finalization mechanism. * * If the garbage collector determines at a certain point in time that the * referent of a phantom reference is phantom reachable, then at that * time or at some later time it will enqueue the reference. * * In order to ensure that a reclaimable object remains so, the referent of * a phantom reference may not be retrieved: The get method of a * phantom reference always returns null. * * Unlike soft and weak references, phantom references are not * automatically cleared by the garbage collector as they are enqueued.An * object that is reachable via phantom references will remain so until all * such references are cleared or themselves become unreachable. * * @authorMark Reinhold * @since1.2 */public class PhantomReference extends Reference {/** * Returns this reference object's referent.Because the referent of a * phantom reference is always inaccessible, this method always returns * null. * * @returnnull */ public T get() { return null; }/** * Creates a new phantom reference that refers to the given object and * is registered with the given queue. * * It is possible to create a phantom reference with a null * queue, but such a reference is completely useless: Its get * method will always return null and, since it does not have a queue, it * will never be enqueued. * * @param referent the object the new phantom reference will refer to * @param q the queue with which the reference is to be registered, *or null if registration is not required */ public PhantomReference(T referent, ReferenceQueue q) { super(referent, q); }}

通过源码得知
  • 只有一个构造方法和get() 方法
  • get() 方法返回的是null。说明无法通过虚引用来获取对象。
  • 虚引用必须要和ReferenceQueue 引用队列一起使用。
使用场景:管理堆外内存
代码示例:
public class PhantomReferenceTest {public static void main(String[] args) throws Exception { ReferenceQueue queue = new ReferenceQueue(); PhantomReference phantomReference = new PhantomReference<>(new Person("zhangsan"), queue); // 无论何时都为null System.out.println(phantomReference.get()); System.gc(); TimeUnit.SECONDS.sleep(1); // 无论何时都为null System.out.println(phantomReference.get()); }}class Person { private String name; public Person(String name) { this.name = name; }@Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; }@Override protected void finalize() throws Throwable { super.finalize(); System.out.println("Person: " + this + ", is gc"); } }

5. 引用队列
【Java|Java四种引用类型】引用队列可以与软引用、弱引用以及虚引用一起使用,当GC准备回收对象时,如发现它还有引用,就会在回收对象之前,将这个引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已有引用,来判断被引用的对象是否要被GC回收。
6 总结
  • 强引用:创建一个Object对象,只要o引用指向这个对象,就不会被GC回收,即使内存不足时,抛OOM异常也不会被回收。
  • 软引用:内存足够时,软引用不会被回收;只有内存不足时,GC才会回收软引用的对象;如果回收后仍然没有足够内存时,才会抛OOM异常
  • 弱引用:无论内存是否足够,只要JVM进行GC,弱引用关联的对象都会被回收
  • 虚引用:最弱的一种引用关系,如果一个对象持有虚引用,那么它就和没有引用一样,它随时可能会被回收

    推荐阅读