JVM系列之对象的可达性分析算法

248 阅读6分钟

背景

JVM中对象的可达性分析算法不再赘述,其中《深入理解Java虚拟机》这本书中,对于哪些对象可以作为GC Roots有如下描述

那么如下代码:

public static void main(String[] args) {
    if (true) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
    }
    System.gc();
}

执行后控制台输出如下(需要在启动类添加vm参数-XX:+PrintGCDetails):

65536
[GC (System.gc()) [PSYoungGen: 4661K->1153K(38400K)] 70197K->66697K(125952K), 0.0021772 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 1153K->0K(38400K)] [ParOldGen: 65544K->66599K(87552K)] 66697K->66599K(125952K), [Metaspace: 3561K->3561K(1056768K)], 0.0055909 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 333K [0x00000000d5b00000, 0x00000000d8580000, 0x0000000100000000)  eden space 33280K, 1% used [0x00000000d5b00000,0x00000000d5b534a8,0x00000000d7b80000)  from space 5120K, 0% used [0x00000000d7b80000,0x00000000d7b80000,0x00000000d8080000)  to   space 5120K, 0% used [0x00000000d8080000,0x00000000d8080000,0x00000000d8580000) ParOldGen       total 87552K, used 66599K [0x0000000081000000, 0x0000000086580000, 0x00000000d5b00000)  object space 87552K, 76% used [0x0000000081000000,0x0000000085109e98,0x0000000086580000) Metaspace       used 3568K, capacity 4560K, committed 4864K, reserved 1056768K  class space    used 383K, capacity 388K, committed 512K, reserved 1048576K

输出日志的详解可以参考这篇文章点我

从日志[ParOldGen: 65544K->66599K(87552K)]中可以很明显看出来,placeHolder在离开自己的作用域后,所指向的在堆heap中的对象并没有被回收。按照《深入理解Java虚拟机》书中的描述,placeHolder指向的对象作为了GC ROOTS。

而如下代码

public static void main(String[] args) {
    if (true) {
        byte[] placeHolder = new byte[64 * 1024 * 1024];
        System.out.println(placeHolder.length / 1024);
    }
    int replacer = 1;
    System.gc();
}

执行后控制台输出如下

65536
[GC (System.gc()) [PSYoungGen: 4661K->1195K(38400K)] 70197K->66739K(125952K), 0.0019750 secs] [Times: user=0.02 sys=0.02, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 1195K->0K(38400K)] [ParOldGen: 65544K->1063K(87552K)] 66739K->1063K(125952K), [Metaspace: 3561K->3561K(1056768K)], 0.0048424 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 38400K, used 333K [0x00000000d5b00000, 0x00000000d8580000, 0x0000000100000000)  eden space 33280K, 1% used [0x00000000d5b00000,0x00000000d5b534a8,0x00000000d7b80000)  from space 5120K, 0% used [0x00000000d7b80000,0x00000000d7b80000,0x00000000d8080000)  to   space 5120K, 0% used [0x00000000d8080000,0x00000000d8080000,0x00000000d8580000) ParOldGen       total 87552K, used 1063K [0x0000000081000000, 0x0000000086580000, 0x00000000d5b00000)  object space 87552K, 1% used [0x0000000081000000,0x0000000081109e88,0x0000000086580000) Metaspace       used 3568K, capacity 4560K, committed 4864K, reserved 1056768K  class space    used 383K, capacity 388K, committed 512K, reserved 1048576K

从日志[ParOldGen: 65544K->1063K(87552K)]中可以很明显看出来,placeHolder在离开自己的作用域后,所指向的在堆heap中的对象被回收。按照《深入理解Java虚拟机》书中的描述,placeHolder指向的对象已经作为了GC ROOTS,那么这个GC ROOTS对象是如何回收的呢?

局部变量表

关于局部变量表的介绍查看此篇文章点我

栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的局部变量就有可能会复用过期局部变量的槽位,从而达到节省资源的目的

上述第一段代码的局部变量表

LocalVariableTable:
Start  Length  Slot  Name   Signature
    0      21     0  args   [Ljava/lang/String;
    5      12     1 placeHolder   [B

上述第二段代码的局部变量表

LocalVariableTable:
Start  Length  Slot  Name   Signature
    0      23     0  args   [Ljava/lang/String;
    5      12     1 placeHolder   [B
   19       4     1 replacer   I

placeHolder在if语句执行完以后,这个局部变量就过了它的作用域。因此,replacer重用了placeHolder Slot的位置。因此猜测

局部变量表中的变量也是重要的垃圾回收根节点,只要被其引用或间接引用的对象都不会被回收

那么为何第二段代码中,placeHolder所引用的对象被垃圾回收了呢?因为placeHolder被JVM从栈帧的局部变量表中移除掉了,此时placeHolder所引用的对象不再作为GC Roots,所以会被垃圾回收掉。