avatar

目录
Java 的4种引用类型

俗话说,温故而知新。该篇是《码出高效——Java开发手册》的读书笔记,主要是对Java中四种引用类型作个总结和回顾.

Java语言是一门OOP的静态强类型语言。在Java中,我们常常通过

Code
1
Object obj = new Object()

这样的方式实例化一个对象。在JVM遇到new指令时,会触发其在堆内存上分配内存空间,设置零值(如引用类型初始设置为null),设置对象的元数据信息,最后执行初始化方法调用构造函数完成初始化工作,并通过obj这样一个引用变量关联到堆内存上的对象首地址。

通常来讲,对象在堆上被创建之后就持有一个引用。如果一个对象从GCRoots开始到其自身的引用链上不可达,那么该对象就会被JVM标记为可回收对象,在下一次GC来临时被回收掉。根据对象持有的引用类型的不同,相应的,对象在GC之间的存活时间也不同。具体来说,Java的引用类型可以分为以下四种:

  1. 强引用(Strong Referencce)
  2. 软引用 (Soft Reference)
  3. 弱引用 (Weak Reference)
  4. 虚引用 (Phantom Reference)

1. 强引用

形如

Code
1
Object obj = new Object()

其实就是强引用关系。只要obj这个引用不被主动置为 null, 并且处于GC Roots 可达的状态,那么在Java虚拟机进行GC操作时,即使内存耗尽,也不会回收该对象。

2. 软引用

软引用的引用力度弱于“强引用”,是用在非必须对象的场景中。在OOM之前,垃圾回收器会把这些软引用指向的对象加入回收范围,以获得更多的内存空间。

3. 弱引用

弱引用的引用力度与上述二者相比就更弱了一些,其特点是在垃圾回收器进行年轻代回收时,如果当前被弱引用指向的对象只存在着弱引用这一引用路径的化,则该对象一定会被回收。

4. 虚引用

虚引用是最最最弱的一种引用关系(虽然我也不知道它的具体用处是啥),用虚引用完成一个对象的定义之后,随即就无法通过该引用获取到该对象,因此也有“幽灵引用”的说法。

下面给出一段SoftReference和WeakReference的使用代码,来证明一下软引用和弱引用在内存紧张的情况下其回收能力的强大。

Code
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
public class SoftReferenceDemo {

public static void main(String[] args) {

//List<SoftReference> houses = new ArrayList<>(); // 1
List<House> houses = new ArrayList<>();

int i = 0;
while (true) {
//SoftReference<House> buyer = new SoftReference<>(new House()); //1
//houses.add(buyer); //1
House house = new House();
houses.add(house);
++i;
System.out.println("i="+i); // 使用 SoftReference i=333296 OOM
// 不使用 SoftReference i=2400 OOM
}
}

}

class House {
private static final Integer DOOR_NUMBER = 2000;
public Door[] doors = new Door[DOOR_NUMBER];

class Door {}
}

设置JVM运行参数为 -Xmx20M 并运行上述程序. 可以看到当使用软引用时(即打开注释1并把对应的强引用代码注释), i的计数可以去到35000左右才发生OOM. 相反如果直接强引用,则计数i到2400的时候就已经OOM了。这表明了在内存紧张的情况下,使用软引用能够使内存空间更快更好地被释放,从而支撑了程序的正常运行。那为什么使用了软引用最后依然会OOM呢?这是因为虽然注释1处的确使用了软引用,但是它本身还是占有一定内存的,而且这些个SoftReference 是被最外面的List这个强引用劫持着的,在不断地执行add操作后,最终产生了OOM.

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SoftReferenceIdleDemo {

public static void main(String[] args) {

House house = new House();
SoftReference<House> sfr = new SoftReference<>(house);
house = null;
while (true) {
System.gc(); //建议JVM尽快进行垃圾回收
System.runFinalization(); //强制调用已失去引用对象地finalize()方法

if (sfr.get() == null) {
System.out.println("house is null.");
} else {
System.out.println("house is still here.");
}
}
}
}

这第二段代码段会一直输出 “house is still here“,这个现象则向我们展示了,在强引用对象House的引用已经被置为null的情形下,软引用会一直持有对象new House()的有效引用。那么如果我们想在对象的强引用被置null的情况下能够感知这一动作,并且随之主动断开对同一对象的引用关系的话,显然SoftReference是做不到了,只能拜托WeakReference弱引用了.

下面这段代码可以证明这一点。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class WeakReferenceIdleDemo {


public static void main(String[] args) {

House house = new House();
WeakReference<House> wkr = new WeakReference<>(house);
house = null;
long start = System.nanoTime();
int count = 0;
while (true) {
if (wkr.get() == null) {
long duration = (System.nanoTime() - start) / (1000 * 1000);
System.out.println("house is null and existed " +duration+" ms.");
break;
} else {
System.out.println("house is still here, count=" + (++count));
}
}
}
}

添加JVM 启动参数 -XX:PrintGCDetails 打印GC回收细节, 运行上述代码可得到以下类似结果:

house is still here, count=85006
[GC (Allocation Failure) [PSYoungGen: 32768K->840K(37888K)] 32768K->848K(123904K), 0.0011459 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
house is still here, count=85007
house is null and existed 639 ms.

这说明,在YGC阶段,WeakReference指向的new House()对象是真的被回收了。


参考:
《码出高效——Java开发手册》 章节 7.5.1

文章作者: JanGin
文章链接: http://jangin.github.io/2020/08/24/4-reference-type-in-Java/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 JanGin's BLOG

评论