概述
GC root
所谓“GC roots”,或者说tracing GC的“根集合”,就是一组必须活跃的引用。
注意,是一组必须活跃的引用,不是对象。
而且对于年轻代GC,GC ROOT必须是年轻代对象的引用。
对于老年代GC, GC ROOT必须是老年代对象的引用。
GC root枚举
以young gc为例(old gc同理):
在进行gc trace之前,必须对gc root进行枚举,保证年轻代里所有存活的对象都能被标记到。
-
JVM使用OoMap记录了负责记录堆外对堆内的引用,遍历OoMap,将其中属于年轻代的引用设置为GC root。
-
JVM再遍历老年代中的所有对象(先不考虑Remember Set),将老年代所持有的年轻代对象的引用也设置为GC root。
为什么要遍历老年代的所有对象,而不直接使用OoMap中的老年代引用进行dfs搜索?
OoMap中记录了所有堆外对堆内的引用,包括老年代和年轻代,在GC root枚举时,我们忽略了老年代引用。但是为什么不直接使用老年代引用进行dfs搜索,找出相关的跨代引用呢?这不比遍历老年代所有对象更快速吗?
要回答这个问题,要理解跨代引用的两种方式:
- OoMap -》老年代对象 -》年轻代对象
- OoMap -》年轻代对象1 -》老年代对象 -》年轻代对象2
我们知道在GC Trace的时候,如果碰到老年代的对象,则停止trace,所以减少了老年代内存空间的扫描。
-
所以对于第1中方式的跨代引用,老年代对象在OoMap中记录了,所以利用OoMap中的老年代对象进行dfs搜索跨代引用,是可以搜索到对应的年轻代,将其引用加入GC ROOT。
-
但是对于第2种方式的跨代引用,OoMap中没有记录老年代对象,所以无法找到对应的年轻代对象2,无法将其引用加入GC ROOT。虽然年轻代对象1是GC root,但是其在trace的过程中,搜索到老年代对象时就停止,所以年轻代对象2无法被标记。
由上述我们可以知道,必须对老年代的所有对象进行遍历,判断其是否有跨代引用,但是这同时也让一个垃圾内存无法回收,比如老年代的垃圾对象具有跨代引用,那它指向的年轻代对象也无法回收。
所以为了不遍历老年代的所有对象,使用了Remember Set记录老年代对年轻代的引用,减少了扫描的范围。
但是对于CMS这种old gc的实现,由于其没有实现年轻代-》老年代的Remember Set,所以其必须要遍历年轻代的所有对象,寻找跨代引用,将其加入GC ROOT。
GC Trace
GC优化
GC优化的核心思路在于:尽可能让对象在新生代中分配和回收,尽量避免过多对象进入老年代,导致对老年代频繁进行垃圾回收,同时给系统足够的内存减少新生代垃圾回收次数。
young gc频繁:
- young gc后年轻代对象很少:
说明内存中短期对象很多,增大年轻代,young GC时间更多取决于GC后存活对象的数量,而非Eden区的大小。 - young gc后年轻代对象很多:
说明内存中长期对象很多,young gc无法对其收集,可以减少对象晋升年龄(动态年龄判断有相似的作用),将长期对象早点进行老年代,年轻代供短期对象使用。也可降低年轻代,增加老年代的大小。
full gc频繁:
- full gc后老年代对象很少:
说明老年代进入了大量短期对象,所以增大年轻代/Survivor空间。 - full gc后老年代对象很多:
系统中具有大量的长期对象,降低年轻代,增加老年代的大小,或者增加整体的内存大小。
参考:
http://ericfu.me/g1-garbage-collector
https://segmentfault.com/q/1010000040841261
https://www.zhihu.com/question/53613423/answer/135743258
https://blog.csdn.net/wtopps/article/details/109186517
https://tech.meituan.com/2017/12/29/jvm-optimize.html
最后
以上就是淡然睫毛膏为你收集整理的对于JAVA GC的理解的全部内容,希望文章能够帮你解决对于JAVA GC的理解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复