Java中有四种引用类型:强引用、软引用、弱引用、虚引用。
强引用是最常见的一种引用类型,在实际开发中,几乎都是强引用类型。
Object obj = new Object();
当我们的对象有强引用的时候,即使内存不足,JVM宁愿抛出OOM,也不会把该对象回收掉。如果需要让垃圾回收器回收此对象,需要将强引用关系打破,最简单的方式就是将对象赋值为null。下面写个例子做个简单说明:

创建一个Order对象,为了更好的知晓垃圾回收的情况,这里重写 finalize() 方法,因为对象被垃圾回收时都会调用此方法。
- /**
- * 订单对象
- */
- public class Order {
- @Override
- protected void finalize() throws Throwable {
- System.out.println("order对象被垃圾回收");
- }
- }
当对象的强引用关系被打破时,显式调用垃圾回收器
- /**
- * 强引用类型垃圾回收
- */
- public class NormalReferenceTest {
- public static void main(String[] args) throws IOException {
- Order order = new Order();
- // 破坏引用关系
- order = null;
- // 调用垃圾回收器,进行垃圾回收
- System.gc();
- System.out.println(order);
-
- // 阻塞Main主线程
- System.in.read();
-
- }
- }
输出结果:
- null
- order对象被垃圾回收
根据输出结果,可知Order对象被垃圾回收掉了。
注意:
在生产环境上,一定不要去重写 finalize() 方法,有内存溢出的风险。由于每个对象在进行垃圾回收时都会去调用该对象的 finalize() 方法,该方法默认是空的,啥都没做,执行效率是非常快的。但是如果重写后就会影响垃圾回收的效率,假设此时业务上还有大量的对象产生,垃圾回收的效率小于对象产生的效率,时间一长就会内存溢出。

软引用就是用 SoftReference 将引用对象包装一下,通过 get() 方法获取包装的对象,使用方式如下:
- SoftReference
softReference = new SoftReference<>(new byte[1024 * 1024 * 10]); - softReference.get();//获取软引用对象
这里用一个例子说明软引用的特点
- -Xmx20M
- SoftReference
- get()
- get()
- get()
代码如下:
- public class SoftReferenceTest {
- public static void main(String[] args) {
- SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);// 10M
- // 获取软引用对象
- System.out.println(softReference.get());
- // 调用垃圾回收
- System.gc();
- // 睡眠一下,给与垃圾回收时间
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 再次获取软引用对象
- System.out.println(softReference.get());
-
- // 再new一个字节数组,堆内存不够,会触发垃圾回收,如果内存空间还不够,就会把软引用引用的对象回收掉
- byte[] bytes = new byte[1024 * 1024 * 12];// 12M
- // 再次获取软引用对象,此时将会获取为null
- System.out.println(softReference.get());
-
- }
- }
运行结果:
- [B@677327b6
- [B@677327b6
- null
说明:
第一次获取软引用对象是有值的,接着调用GC,GC垃圾回收后,第二次获取也是有值的,说明垃圾回收时先不会回收软引用对象。随后,继续往内存里面new一个12M的byte数组,显然,我们堆内存是20M,最开始的软引用对象占10M,这里在new 一个,内存是装不下的,所以触发了垃圾回收器回收10M的软引用对象,第三次获取软引用对象时为null,因为都已被垃圾回收器回收掉了。
软引用的特点:当JVM进行垃圾回收时,如果第一次回收后内存足够,那么不会回收软引用对象;如果第一次回收后内存依旧不够,那么就会回收掉软引用对象。根据这个特点,软引用可以用来做缓存,当系统内存足够时,通过缓存获取值,如果内存不够时,先回收掉缓存,释放一定的内存空间,延迟OOM。

弱引用就是通过 WeakReference 包装了一下,使用如下:
- WeakReference
weakReference = new WeakReference<>(new Order()); - // 获取弱引用的值
- System.out.println(weakReference.get());
弱引用和软引用最大的区别就是无论内存是否足够,弱引用都会被GC回收
- /**
- * 弱引用
- */
- public class WeakReferenceTest {
- public static void main(String[] args) {
- WeakReference
weakReference = new WeakReference<>(new Order()); - // 获取弱引用的值
- System.out.println(weakReference.get());
- // 垃圾回收
- System.gc();
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 获取弱引用的值
- System.out.println(weakReference.get());
- }
- }
运行结果:
- com.test.soft.Order@677327b6
- order对象被垃圾回收
- null
弱引用在WeakHashMap,ThreadLocal中有使用到。
Phantom 虚幻的
虚引用又叫做幻影引用,它需要配合一个队列来使用,但是我们 无法通过虚引用来获取一个对象的真实引用 ,我们来看下面的代码
- ReferenceQueue
referenceQueue = new ReferenceQueue<>(); -
- // 虚引用在垃圾回收的时候,会被放到一个队列里,这个队列供垃圾回收器做特殊处理
- PhantomReference
phantomReference = new PhantomReference<>(new Order(), referenceQueue); - System.out.println(phantomReference.get());// 这里输出结果为null
上述代码第五行的输出结果居然为null,进入到源码里面会发现
- 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. - *
- * @return
null - */
- public T get() {
- return null;
- }
- }
这居然直接返回的null,虚引用不能通过get()方法获取到值,那到底有何存在的意义呢?
我们来看下面的代码:
-Xmx20MReferenceQueue 队列,创建虚引用,将 new Order() , referenceQueue 作为参数传入list 集合中放入数据,时间长了堆内存会满,触发垃圾回收ReferenceQueue 队列里面的值- /**
- * 虚引用
- */
- public class PhantomReferenceTest {
-
- public static void main(String[] args) {
- List
- ReferenceQueue
referenceQueue = new ReferenceQueue<>(); -
- // 虚引用在垃圾回收的时候,会被放到一个队列里,这个队列供垃圾回收器做特殊处理
- PhantomReference
phantomReference = new PhantomReference<>(new Order(), referenceQueue); - System.out.println(phantomReference.get());
-
- new Thread(() -> {
- while (true) {
- // 不断往list添加数据。内存会满,此时会进行垃圾回收,虚引用就会被放在队列里面
- list.add(new byte[1024 * 1024]);
- System.out.println(phantomReference.get());
- }
- }).start();
-
- new Thread(() -> {
- while (true){
- // 从队列里面读数据
- Reference extends Order> poll = referenceQueue.poll();
- if (poll != null){
- System.out.println("虚引用对象被JVM回收" + poll);
- }
- }
- }).start();
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- }
- }
-
- 复制代码
- 输出结果:
-
- null
- null
- null
- null
- null
- null
- null
- null
- null
- null
- null
- null
- null
- order对象被垃圾回收
- null
- null
- null
- null
- null
- 虚引用对象被JVM回收java.lang.ref.PhantomReference@12bc0828
- Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
- at com.test.soft.PhantomReferenceTest.lambda$main$0(PhantomReferenceTest.java:29)
- at com.test.soft.PhantomReferenceTest$$Lambda$1/1831932724.run(Unknown Source)
- at java.lang.Thread.run(Thread.java:748)
-
虚引用特点:
当垃圾回收器准备回收一个对象,如果发现它还有虚引用,那么就会在回收该对象之前,把这个虚引用加入到与之关联的 ReferenceQueue 中。在 NIO 中,就用了虚引用来管理堆外内存。