关于Java中引用的面试题

一、介绍

在Java中,有以下四种类型的引用强软弱虚

  1. 强引用(Strong Reference):最常见的引用类型,也是默认的引用类型。如果一个对象具有强引用,那么垃圾回收器就不会回收这个对象。

  2. 软引用(Soft Reference):如果一个对象具有软引用,那么当系统内存不足时,垃圾回收器会尝试回收该对象。软引用通常用于缓存中,以便在内存紧张时释放一些缓存。

  3. 弱引用(Weak Reference):如果一个对象具有弱引用,那么它的生命周期更短,它在任何时候都可能被垃圾回收器回收。弱引用通常用于外部引用内部对象时使用,以免内存泄漏。

  4. 虚引用(Phantom Reference):虚引用是所有引用类型中最弱的一种。如果一个对象具有虚引用,那么它就像没有被引用一样,随时会被垃圾回收器回收。虚引用主要用于跟踪对象被回收的状态。

上面属于Java的面试八股文,那么在面试之中,我们该如何进行理解输出呢?

二、引用

1)强引用(Strong Reference)

在日常开发中最为平常的引用,因为我们直接new出来的对象就属于强引用。

那么,如果一个对象只要有强引用,那么GC就不会回收掉它。如下这个类

1
2
3
4
5
6
7
8
9
10
package com.banmoon.reference;

public class Reference {

@Override
protected void finalize() throws Throwable {
System.out.println("GC回收");
super.finalize();
}
}

我只有将引用设置为null后,GC才能回收掉它,强引用就是如此。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon.reference;

import java.io.IOException;

public class StrongReference {

public static void main(String[] args) throws IOException {
Reference reference = new Reference();
System.out.println(reference);
// 赋值为null,取消对象的强引用
reference = null;
// 通知进行gc
System.gc();
System.out.println(reference);
// 阻塞一下
System.in.read();
}

}

2)软引用(Soft Reference)

软引用和强引用不同

  • 强引用只要有引用指向对象,对象就不会被回收

  • 而软引用,就算有软引用指向对象,在发生内存不足的时候,GC就会把这些软引用的对象给回收

为了测试下面的代码,我们需要添加一点JVM参数,限制一下JVM的内存,即-Xms20M -Xmx20M,我限制了20M的内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.banmoon.reference;

import java.lang.ref.SoftReference;
import java.util.concurrent.TimeUnit;

public class SoftReferenceMain {

public static void main(String[] args) throws InterruptedException {
// 一个byte数组,内容大小是10M
SoftReference<byte[]> sr = new SoftReference<>(new byte[1024 * 1024 * 10]);
// 获取数组
System.out.println(sr.get());
// 睡眠一秒后,再次查看数组
TimeUnit.SECONDS.sleep(1);
System.out.println(sr.get());

// 我再创建一个数组,12M,再次查看原来软引用的数组
byte[] bytes = new byte[1024 * 1024 * 12];
System.out.println(sr.get());
}

}

image-20230527221108072

3)弱引用(Weak Reference)

比起上面的两个引用,弱引用可以这样理解,它引用的对象,只要发生GC,就都会被回收。

也就是说,前两个引用都一定程度上保护了对象,但弱引用不行,弱引用保护不了任何对象。

在平常的使用中,基本没啥用,当然ThreadLocal中使用到了,搭配着强引用一起进行使用的。

简单改造一下强引用的代码,变成弱引用,GC后会发生什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon.reference;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;

public class WeakReferenceMain {

public static void main(String[] args) throws IOException, InterruptedException {
WeakReference<Reference> wr = new WeakReference<>(new Reference());
System.out.println(wr.get());
// 通知进行gc,并阻塞一下
System.gc();
TimeUnit.SECONDS.sleep(1);
// 查看输出
System.out.println(wr.get());
}

}

image-20230527221746422

4)虚引用(Phantom Reference)

最后一个虚引用,比较特殊。主要是给GC使用的,对的没错,JVMGC的时候,也会创建对象,这些基本就是虚引用。

下面作为示例了解一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.reference;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceMain {

public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Reference> queue = new ReferenceQueue<>();
PhantomReference<Reference> phantom = new PhantomReference<>(new Reference(), queue);

//检查垃圾回收队列中是否已经加入该对象
System.out.println("Is queued: " + phantom.isEnqueued());

//强制进行垃圾回收并等待GC完成
System.gc();
Thread.sleep(1000);

//检查垃圾回收队列中是否已经加入该对象
System.out.println("Is queued: " + phantom.isEnqueued());

//从队列中读取并打印垃圾回收的信息
System.out.println(queue.poll());
}

}

虚引用的使用场景

虚引用通常用于实现比弱引用更加精细的对象 finalization(终结)处理逻辑。虚引用通常与引用队列结合使用,对于一个具有虚引用的对象,当垃圾回收器准备回收该对象时,如果发现它存在虚引用,就会在回收对象的内存之前,将这个虚引用加入到与之关联的引用队列中。

在实际应用中,虚引用常用于:

  1. 用于在对象被回收时进行一些定制操作,例如发送通知、记录日志、清理资源等等。

  2. 用于避免内存泄漏,通过使用虚引用表示该对象将会被垃圾回收器回收,并触发一些清理操作。

值得注意的是,虚引用并不会影响被引用对象的生命周期。当垃圾回收器准备回收对象时,虚引用会被加入到与之关联的引用队列中,但此时虚引用本身并不能保证被回收,需要不断调用getReference()方法来获取引用队列中的虚引用,直到返回null为止。

三、最后

关于GC回收强引用的对象,有时候就算被强引用,也还是会被回收的场景,比如说循环引用。所以还是得具体情况,具体分析。

我是半月,你我一同共勉!!!