关于gc roots的理解

                                                                      gc roots是什么

所谓“GC roots”,或者说tracing GC的“根集合”,就是一组必须活跃的引用
例如说,这些引用可能包括:

所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。

VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。

JNI handles,包括global handles和local handles

(看情况)所有当前被加载的Java类

(看情况)Java类的引用类型静态变量

(看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)

(看情况)String常量池(StringTable)里的引用

注意,是一组必须活跃的引用,不是对象。

Tracing GC的根本思路就是:给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,其余对象(也就是没有被遍历到的)就自然被判定为死亡。注意再注意:tracing GC的本质是通过找出所有活对象来把其余空间认定为“无用”,而不是找出所有死掉的对象并回收它们占用的空间。
GC roots这组引用是tracing GC的起点。要实现语义正确的tracing GC,就必须要能完整枚举出所有的GC roots,否则就可能会漏扫描应该存活的对象,导致GC错误回收了这些被漏扫的活对象。

这就像任何递归定义的关系一样,如果只定义了递推项而不定义初始项的话,关系就无法成立——无从开始;而如果初始项定义漏了内容的话,递推出去也会漏内容。

那么分代式GC对GC roots的定义有什么影响呢?
答案是:分代式GC是一种部分收集(partial collection)的做法。在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC roots的一部分。
具体到分两代的分代式GC来说,如果第0代叫做young gen,第1代叫做old gen,那么如果有minor GC / young GC只收集young gen里的垃圾,则young gen属于“收集部分”,而old gen属于“非收集部分”,那么从old gen指向young gen的引用就必须作为minor GC / young GC的GC roots的一部分。
继续具体到HotSpot VM里的分两代式GC来说,除了old gen到young gen的引用之外,有些带有弱引用语义的结构,例如说记录所有当前被加载的类的SystemDictionary、记录字符串常量引用的StringTable等,在young GC时必须要作为strong GC roots,而在收集整堆的full GC时则不会被看作strong GC roots。

换句话说,young GC比full GC的GC roots还要更大一些。如果不能理解这个道理,那整个讨论也就无从谈起了。

                                                                           gc roots是什么 

常说的GC(Garbage Collector) roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC roots且没有被GC roots引用的对象。

一个对象可以属于多个root,GC root有几下种:

  • Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots,.
  • Thread - 活着的线程
  • Stack Local - Java方法的local变量或参数
  • JNI Local - JNI方法的local变量或参数
  • JNI Global - 全局JNI引用
  • Monitor Used - 用于同步的监控对象
  • Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此就只有留给分析分员去确定哪些是属于"JVM持有"的了。

                                                               GC是如何处理循环引用的吗?

①首先说一下,GC里边在JVM当中是使用的ROOT算法,ROOT算法,什么称作为ROOT呢,就是说类的静态成员,静态成员就是static修饰的那种,是“根”的一个,根还包括方法中的成员变量,只有成员或对象不挂在根上,GC的时候就可能把他们搞掉,这里提到的循环引用,就看这个循环引用是否挂在根上,如果挂在根上,如果这个根还被JVM的Java代码所执行的话,就不会GC掉,如果说这个根已经被释放掉了,这个对象不挂在跟上了,那个这个对象就会被GC掉。

②说一下根搜索算法,ROOTS,这个算法,那些在Java里会被认为是根呢,在我印象里一般是static修饰的类成员,比如说静态字段,这种字段引用的对象被称为根,只要类在POOL区里不被卸载,一直在堆里,类对象只要没被回收掉,他引用的对象就不会被GC。

③再说另一种情况,方法中的栈,栈中有他的栈成员 Integer  a = XXX,当方法没有被释放,没有出栈的时候,方法没有被弹出的时候,那Integer a 所引用的对象也是不会被回收的,在什么情况下回收呢,就是这个对象没有挂在根上,就会被回收。

④我们回到标题的问题,这个循环引用是否被回收,就看这个循环引用是否挂在根上,A引用B,B引用A,A和B并没有挂在某个内存元和根上,当他们的生命周期结束的时候,这两个对象都有可能被回收。

⑥具体回收的机制,就比较复杂了,每次GC的时候,对要被回收的对象标记一次,比如说会有个计数器每次+1,+1,+1,每次GC的时候就+1一次,当对象达到默认值了,比如说好像15次吧,在新生带创建的对象达到15次了就会被达到老年带里去,而老年代对象的回收的频率和新生带回收的频率是不一样的,可以仔细看下图中pool里的分区,了解他们的运行机制。

注:JVM heap分区块

Generation代
 - YongGeneration/NewGeneration:新生代,在Eden/S0/S1的存活的对象。
 - OldGeneration:老年代,在Tenured区存活的对象。
 - PermanentGeneration:永久代。
Space 区
 - Eden:伊甸园区,是新生代的一个区。
 - Survivor:幸存区,属于新生代,为了复制算法的需要。一般分成大小相等的两个区(S0/S1或者From/To)。
 - Tenured:存放老年代的区域。
 - Permanent:终身区。

 
下图:Hotspot 的 Heap 分区


参考:https://blog.csdn.net/leishenop/article/details/53728605、https://blog.csdn.net/kkgbn/article/details/44787149、

https://blog.csdn.net/fenglibing/article/details/8928927

发布了740 篇原创文章 · 获赞 65 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_41723615/article/details/104373135