【JVM】根可达算法

【JVM】根可达算法 根可达性算法可以理解为一句话从一批“绝对不能被回收的对象”出发顺着引用关系往外找能找到的对象就是存活对象找不到的就是垃圾对象。一、什么是 GC RootGC Root 就是垃圾回收时的“起点对象”。JVM 在判断对象是否存活时不是看这个对象有没有被别人引用而是看这个对象能不能从 GC Root 出发通过引用链找到。比如A-B-C如果A是 GC Root那么 JVM 从A出发可以找到B再找到C。所以A、B、C 都是存活对象但是如果有D-EE-D虽然D和E互相引用但如果从任何 GC Root 都找不到它们那么它们就是垃圾。这也解决了引用计数法无法解决的循环引用问题。二、根可达性算法的判断过程可以分成三步1. 找出 GC RootJVM 先找到一批根对象比如线程栈里的局部变量 静态变量 常量引用 JNI 本地方法引用 被 synchronized 加锁的对象这些对象被认为是程序当前执行过程中“还可能用到的对象”。2. 从 GC Root 出发沿引用链查找例如publicclassDemo{staticObjectstaticObjnewObject();publicstaticvoidmain(String[]args){ObjectlocalObjnewObject();ObjectinnerObjnewObject();localObjinnerObj;}}这里可能形成这样的引用关系GC Root | |-- staticObj | |-- main线程栈中的 localObj只要对象能从这些 GC Root 找到就不会被回收。3. 找不到的对象就是垃圾例如ObjectanewObject();ObjectbnewObject();anull;bnull;当a和b都不再引用原来的对象后如果这些对象也没有被其他 GC Root 间接引用那么它们就变成垃圾对象。三、哪些对象可以作为 GC Root你写的四类可以这么理解。1. 系统类的实例对象这个说法可以理解为由系统类加载器加载的一些核心类对象或者方法区中类静态属性引用的对象可以作为 GC Root。更常见的面试说法是方法区中类静态属性引用的对象。比如publicclassUserService{publicstaticObjectobjnewObject();}这里的obj是静态变量属于类级别的变量。只要UserService这个类还被加载着那么UserService.class - static obj - new Object()这个new Object()就可以从 GC Root 找到所以不会被回收。可以简单记成静态变量引用的对象可以作为 GC Root 直接或间接可达的对象2. 本地方法调用相关的实例对象也就是本地方法栈中 JNI 引用的对象。Java 有时会调用 Native 方法也就是用 C/C 写的方法比如publicnativevoidmethod();如果 Native 方法中持有了某个 Java 对象的引用那么 JVM 不能随便回收这个对象。因为 Native 代码可能还在使用它。所以这类对象也可以作为 GC Root。可以简单理解为被 native 方法引用的 Java 对象不能被回收3. 当前活动线程相关的实例对象这是最常见、最重要的一类。准确说是虚拟机栈中栈帧里的局部变量表引用的对象可以作为 GC Root。例如publicvoidtest(){ObjectobjnewObject();}当test()方法正在执行时obj是局部变量存放在当前线程的栈帧中。此时当前线程栈帧中的 obj - new Object()所以这个对象不能被回收。再比如publicvoidtest(){UserusernewUser();user.setName(Tom);}只要test()方法还没执行结束user引用的对象通常就是可达的。可以简单记成正在运行的方法中的局部变量引用的对象不能回收4. 被加锁的实例对象也就是被 synchronized 持有的对象可以作为 GC Root。例如ObjectlocknewObject();synchronized(lock){// 临界区代码}当线程进入synchronized(lock)代码块后lock对象正在作为锁使用。这个对象不能被回收否则锁的语义就出问题了。所以 JVM 会认为被锁持有的对象是可达的。可以简单理解为正在被 synchronized 当锁用的对象不能被回收四、举个完整例子publicclassDemo{staticObjectstaticObjnewObject();publicstaticvoidmain(String[]args){ObjectlocalObjnewObject();ObjectuselessObjnewObject();uselessObjnull;}}在main方法执行期间GC Roots | |-- staticObj - Object1 | |-- main线程栈中的 localObj - Object2所以Object1 不会被回收 Object2 不会被回收而ObjectuselessObjnewObject();uselessObjnull;原来被uselessObj引用的对象已经找不到了。所以Object3 可能被回收注意是可能被回收不是立刻回收。因为 GC 什么时候发生由 JVM 决定。五、面试回答版本你可以这样说Java 判断对象是否为垃圾主要使用根可达性算法。JVM 会从一组 GC Roots 对象出发沿着对象之间的引用链向下搜索。如果某个对象能够从 GC Roots 到达说明它仍然被使用不能回收如果某个对象无法从任何 GC Roots 到达那么它就可以被判定为垃圾对象。常见的 GC Roots 包括虚拟机栈中局部变量引用的对象、方法区中静态变量或常量引用的对象、本地方法栈中 JNI 引用的对象以及被 synchronized 锁持有的对象等。更口语一点简单来说就是从线程栈、静态变量、常量、Native 方法引用、锁对象这些根对象开始找能找到的对象就是活的找不到的对象就是垃圾。你这四类可以稍微改得更标准一些1. 虚拟机栈中局部变量表引用的对象 2. 方法区中类静态属性引用的对象 3. 方法区中常量引用的对象 4. 本地方法栈中 JNI 引用的对象 5. 被 synchronized 持有的对象其中你写的当前活动线程相关的实例对象对应的是虚拟机栈中的局部变量引用对象。你写的系统类的实例对象更标准地说应该是方法区中静态变量或常量引用的对象。