Java基础 - Reference

概述

在 Java 中有 4 种引用类型,主要是在垃圾回收时 JVM 会根据不同的引用类型采取不同的措施。下面分别对这四种类型进行说明,特别是弱引用,将会结合示例进行分析。

引用类型

Java 中的四种引用类型如下图所示:

强引用

强引用是 Java 默认的引用类型。如果一个对象具有强引用,当内存空间不足时,JVM 宁可抛出 OutOfMemoryError 错误使程序异常终止,也不会回收具有强引用的对象来解决内存不足问题。

1
2
3
4
public void sayHello(){
Object object = new Object();
// ....
}

如上所示,在一个方法中有一个强引用 object ,该引用保存在栈中,而真正的引用内容 Object 对象保存在堆中。当上述方法运行完成后退出方法栈,强引用 object 就不存在了,引用的 Object 对象就会被回收。 注意,如果 Object object = new Object(); 是全局的,那么在不使用该对象时需要设置 object = null 以帮助垃圾收集器回收该对象。

也就是说,一个对象从根路径能找到强引用指向它,JVM 就不会回收。

软引用

如果一个对象只具有软引用,在内存空间足够的情况下 JVM 就不会回收它;如果内存空间不足就会回收这些对象。更准确地来说,进行 Young GC 不会触发软引用所指向对象的回收;但如果触发Full GC,则软引用所指向的对象将被回收。前提是除了软引用之外没有其他强引用引用的情况下。

1
2
3
4
5
6
7
8
9
// 强引用
String str = "hello world!";
// 软引用
SoftReference<String> softReference = new SoftReference<>(str);
// 内存不足
if(isNotFullMemeory()){
str = null;
System.gc();
}

如上所示,在内存不足时会回收软引用。软引用可用来实现高速缓存。

弱引用

如果一个对象只有弱引用指向它,当进行年轻代垃圾回收时,一旦发现该引用指向的对象就会立刻回收,不管当前内存空间是否足够。注意,由于垃圾回收器是一个优先级很低的线程,因此不一定及时发现那些具有弱引用的对象。

1
2
3
4
5
6
7
8
// 强引用
String str = "hello world!";
// 弱引用
WeakReference<String> weakReference = new WeakReference<>(str);

// 垃圾回收
str = null;
System.gc();

如上所示,在进行垃圾回收时发现弱引用立即回收。如果想使用一个对象且不想介入这个对象的生命周期,这时就可以使用弱引用。注意,下面的代码可以转为强引用。

1
String  str = weakReference.get();

虚引用

虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

1
2
3
4
5
6
7
// 强引用
String str = "hello world!";
// 虚引用
PhantomReference phantomReference = new PhantomReference(str,new ReferenceQueue<>());

str = null;
// 随时可能会输

虚引用主要用来跟踪对象被垃圾回收器回收的活动。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中,该队列就是前文类图中的 ReferenceQueue

再谈弱引用

由于在看 ThreadLocal 源码时考虑到涉及 弱引用 ,因此单独拿出来说明下。下面对 WeakReference 类进行分析。

WeakReference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class WeakReference<T> extends Reference<T> {
/**
* 创建一个指向给定对象的弱引用
*
* @param referent 给定对象的引用
*/
public WeakReference(T referent) {
super(referent);
}

/**
*
* @param referent 给定对象的引用
* @param q 对象被回收后会把弱引用对象,也就是WeakReference对象或者其子类的对象,放入队列ReferenceQueue中,注意不是被弱引用的对象,被弱引用的对象已经被回收了。
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}

通过上述代码强调两个概念:

1 弱引用对象是指 WeakReference 实例或其子类实例。
2 被弱引用的对象是指需要通过 WeakReference 封装的对象,形如:WeakReference<Xxx> appleWeakReference = new WeakReference<>(xxx)。这个时候我们说,持有了 xxx 指向对象的弱引用,也就是说当 xxx 也不再引用(可能不止 xxx 引用)时,就剩下弱引用,此时垃圾回收时是可以把对应的对象回收掉。

基本使用

Fruit 类

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
@Data
@Slf4j
public class Fruit {
/**
* 名称
*/
private String name;

public Fruit(String name) {
this.name = name;
}

@Override
protected void finalize() throws Throwable {
super.finalize();
log.info(name + "finalize !");
}

@Override
public String toString() {
return "Fruit{" +
"name='" + name + '\'' +
'}';
}
}
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
27
28
29
30
31
32
33
34
35
@Slf4j
public class Client {
public static void main(String[] args) throws InterruptedException {

// 强引用
Fruit fruit = new Fruit("小芒果");
// 创建弱引用对象
Animal animal = new Animal(fruit);

// 通过 WeakReference.get() 方法获取对应的对象
log.info("Fruit: " + animal.get());

// 消除强引用,确保只有弱引用(不消除强引用,不会回收)
fruit = null;

// 触发垃圾回收
System.gc();

// 保证 GC 的发生
Thread.sleep(5000);

// 小芒果被回收 - 因此只有弱引用
log.info(animal.get() == null ? " Fruit is Cleared" : animal.get().toString());
}

/**
* 继承 WeakReference ,持有 Fruit 的弱引用。
* 当垃圾回收时,回收的是弱引用 referent 指向的对象,而非 Animal
*/
static class Animal extends WeakReference<Fruit> {
public Animal(Fruit referent) {
super(referent);
}
}
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[main] INFO com.code.concurrent.Client - Fruit: Fruit{name='小芒果'}
[Finalizer] INFO com.code.concurrent.Fruit - 小芒果finalize !
[GC (System.gc()) [PSYoungGen: 5242K->752K(76288K)] 5242K->760K(251392K), 0.0008122 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 752K->0K(76288K)] [ParOldGen: 8K->534K(175104K)] 760K->534K(251392K), [Metaspace: 3371K->3371K(1056768K)], 0.0042356 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
[main] INFO com.code.concurrent.Client - Fruit is Cleared
Heap
PSYoungGen total 76288K, used 7209K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space 65536K, 11% used [0x000000076ab00000,0x000000076b20a738,0x000000076eb00000)
from space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
to space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
ParOldGen total 175104K, used 534K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 0% used [0x00000006c0000000,0x00000006c0085bf8,0x00000006cab00000)
Metaspace used 3857K, capacity 4704K, committed 4864K, reserved 1056768K
class space used 428K, capacity 464K, committed 512K, reserved 1048576K

Process finished with exit code 0

引用队列

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@Slf4j
public class Client {
public static void main(String[] args) throws InterruptedException {

// 强引用
Fruit fruit = new Fruit("小芒果");

// 创建弱引用对象及引用队列
ReferenceQueue<Fruit> referenceQueue = new ReferenceQueue<>();
Animal animal = new Animal(fruit, referenceQueue);

// 通过 WeakReference.get() 方法获取对应的对象
log.info("Fruit 对象信息: " + animal.get());

// GC 前
log.info("GC 前");
if (referenceQueue.poll() == null) {
log.info("没有回收被弱引用的对象,不会加入队列中");
}

// 记录弱引用对象地址,用于回收前后对比
log.info("弱引用对象地址:"+ animal.toString());
// 消除强引用,确保只有弱引用(不消除强引用,不会回收)
fruit = null;

// 触发垃圾回收
log.info("GC 中");
System.gc();
// 保证 GC 的发生
Thread.sleep(5000);

// GC 后
log.info("GC 后");
// 小芒果被回收 - 因此只有弱引用
log.info(animal.get() == null ? " Fruit is Cleared" : animal.get().toString());

Reference reference = null;
if ((reference = referenceQueue.poll()) != null) {
log.info("回收被弱引用的对象,弱引用对象加入队列中,地址为:" + reference.toString());
}
}

/**
* 继承 WeakReference ,持有 Fruit 的弱引用。
* 当垃圾回收时,回收的是弱引用 referent 指向的对象,而非 Animal
*/
static class Animal extends WeakReference<Fruit> {
public Animal(Fruit referent, ReferenceQueue referenceQueue) {
super(referent, referenceQueue);
}
}
}

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[main] INFO com.code.concurrent.Client - Fruit 对象信息: Fruit{name='小芒果'}
[main] INFO com.code.concurrent.Client - GC 前
[main] INFO com.code.concurrent.Client - 没有回收被弱引用的对象,不会加入队列中
[main] INFO com.code.concurrent.Client - 弱引用对象地址:com.code.concurrent.Client$Animal@a09ee92
[main] INFO com.code.concurrent.Client - GC 中
[Finalizer] INFO com.code.concurrent.Fruit - 小芒果finalize !
[GC (System.gc()) [PSYoungGen: 5242K->736K(76288K)] 5242K->744K(251392K), 0.0006983 secs] [Times: user=0.00 sys=0.01, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 736K->0K(76288K)] [ParOldGen: 8K->535K(175104K)] 744K->535K(251392K), [Metaspace: 3380K->3380K(1056768K)], 0.0035821 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[main] INFO com.code.concurrent.Client - GC 后
[main] INFO com.code.concurrent.Client - Fruit is Cleared
[main] INFO com.code.concurrent.Client - 回收被弱引用的对象,弱引用对象加入队列中,地址为:com.code.concurrent.Client$Animal@a09ee92
Heap
PSYoungGen total 76288K, used 7209K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space 65536K, 11% used [0x000000076ab00000,0x000000076b20a738,0x000000076eb00000)
from space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
to space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
ParOldGen total 175104K, used 535K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 0% used [0x00000006c0000000,0x00000006c0085e60,0x00000006cab00000)
Metaspace used 3856K, capacity 4704K, committed 4864K, reserved 1056768K
class space used 428K, capacity 464K, committed 512K, reserved 1048576K

Process finished with exit code 0

小结

本篇文章对 Java 中的 4 种引用进行了介绍,重点对弱引用进行了详细分析,理解了弱引用再去看 ThreadLocal 就能更好地理解其内存泄漏问题。下一篇文章将对 ThreadLocal 进行分析。