JVM

到底什么是GC root?

Posted by Timer on January 10, 2022

前言

对一个东西的了解不应该仅仅是别人给的定义,不然除了字面上的定义外一无所获。

GC Roots

GC Roots的官方定义:

A garbage collection root is an object that is accessible from outside the heap。

借用知乎R大的定义,GC Roots是:

一组必须活跃的引用

最初我看到R大和官方的定义,第一反应是:这两人为什么说的不一致?

现在看来不是他们不一致,而是我理解不到位。

官方的定义是:在堆外可以获得到的对象,说白了就是堆外有引用的对象。

那什么是堆外没有引用的对象?说白了就是把某个引用置null。

Integer x = 123456;
x = null; //此时堆中有对象,但是栈帧中无引用。

R大的定义是:一组必须活跃的引用,先抛开活跃二字不谈,既然有引用,那么堆中必然有对应的对象。

所以两者说法一致,官方从堆的角度来定义,R大从栈的角度来定义。

GC Roots的种类

  • System Class:

    Class loaded by bootstrap/system class loader. For example, everything from the rt.jar like java.util.* .

    简单来说就是被最底层类加载器加载的类的元类型,也就是各个类的Class对象,也就是他们C++中对应的instanceKlass。

  • JNI

  • Thread

    A started, but not stopped, thread.

    运行态的线程,这是是第一点体现“活跃”的地方。

  • Busy Monitor

    Everything that has called wait() or notify() or that is synchronized. For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.

    这里简单来说就是作为锁的对象。

  • Java Local

    Local variable. For example, input parameters or locally created objects of methods that are still in the stack of a thread.

    这里简单来说就是局部变量,官方定义是:当前线程栈中的被创建的变量,这里是第二点体现“活跃”的地方。

所有GC Roots详情见

https://help.eclipse.org/latest/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3

GC Root的可视化

1.通过jps获得当前java程序进程id

2.使用jmap,配合read()阻塞,获得当前堆快照。

3.使用mat分析堆内存。

public class JUCTest {

    public void function0() throws IOException {
        List<Integer> arrayList = new ArrayList<>();
        function1();
        System.in.read(); //获得堆快照 0
    }

    public void function1() throws IOException {
        List<Integer> linkedList = new LinkedList<>();
        System.in.read(); // 获得堆快照 1
    }

    public static void main(String[] args) throws IOException {
        new JUCTest().function0();
    }
}

GC Roots总体布局

image-20220110154605152

可以看出当前程序GC Roots分为四大类,上面已经总结过。

堆快照1分析

image-20220110155822756

堆快照2分析

image-20220110160008864

由此引发的思考

在进行方法调用时,方法调用前后GC root变化会很大,因为弹出栈帧会影响GC root的枚举。

GC root的枚举是依靠oopmap的,oopmap不可能对每条代码都更新一次,它在安全点统一更新。

对于一个栈顶方法,如果它在方法体内调用了System.gc(),这意味着:

  1. 在栈顶方法调用完毕弹出后,到达一个安全点。
  2. oopmap更新,刚刚栈顶方法里的创建的对象会从oopmap中删除。
  3. 利用oopmap完成根节点枚举。
  4. 进行可达性分析。

所以,方法调用后是一个很常见的安全点。

image-20220402231042494