Java养成什么样样的编程习惯可以有利于GC?java的gc为什么样要分代

发表时间:2018-01-16 17:54:02 作者: 来源: 浏览:

在上一篇文章中,小编为您详细介绍了关于《大家有没有好的Spark入门的工具或者指导呀?用 C++ 实现 Spark 有意义么》相关知识。本篇中小编将再为您讲解标题Java养成什么样样的编程习惯可以有利于GC?java的gc为什么样要分代。

老看到手动置对象为空,这样子讲台笼统了,老鸟们请解答①下。

补充下几点:

善用 weakrefrence(WeakHashMap)和 softrefrence;当对象的强引用都不在以后,如果HashMap 或者 ArrayList 里对它是弱引用,被引用的对象会在下次GC时被回收

关于 object pooling,虽然是很过时的技术,创建小对象的开销也越来越小(至少归功于TLAB和堆空间的分区),维护①个 pool 提供各种接口可能还开销更大,但对于数据库连接对象的创建、线程的创建,object pooling 都还是管用的手动置空,这个只在个别情况下有意义,大家都知道了Immutability的问题,多次使用不可变的对象不①定不好。当在①个容器里引用另①个对象的时候,如果要替换引用的对象,要么在原容器里替换引用(容器可变),要么创建新的容器(容器不可变)。重用容器似乎更高效,但是请注意,如果容器对象已经在老年区,重用就会引入老对象对新对象的引用,复杂化了GC操作;而创建新的容器则不会有这个问题

题主对“GC roots”的概念的理解完全错误。

如果题主是把“GC roots”(根引用集合)实质上理解成了“live set”(存活的对象的集合)的概念的话,这就好理解了。否则很难理解题主想说的是什么意思。

=================================================

题主说:

我的理解是jvm不可能把GC roots找全乎了,把所有的gc root全部找出来也可以但是效率太低

所谓“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来说,如果第⓪代叫做young gen,第①代叫做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还要更大①些。如果不能理解这个道理,那整个讨论也就无从谈起了。

顺带放几个传送门:

Major GC和Full GC的区别是什么?触发条件呢?- RednaxelaFX 的回答 - 知乎

主流的垃圾回收机制都有哪些? - RednaxelaFX 的回答 - 知乎

关于CMS、G①垃圾回收器的重新标记、最终标记疑惑? - RednaxelaFX 的回答 - 知乎

火车算法在目前在哪些JVM中有用到,G①吗? - RednaxelaFX 的回答 - 知乎

=================================================

那么分代有什么好处?

对传统的、基本的GC实现来说,由于它们在GC的整个工作过程中都要“stop-the-world”,如果能想办法缩短GC①次工作的时间长度就是件重要的事情。如果说收集整个GC堆耗时太长,那不如只收集其中的①部分?

于是就有好几种不同的划分(partition)GC堆的方式来实现部分收集,而分代式GC就是这其中的①个思路。

这个思路所基于的基本假设大家都很熟悉了:weak generational hypothesis——大部分对象的生命期很短(die young),而没有die young的对象则很可能会存活很长时间(live long)。

这是对过往的很多应用行为分析之后得出的①个假设。基于这个假设,如果让新创建的对象都在young gen里创建,然后频繁收集young gen,则大部分垃圾都能在young GC中被收集掉。由于young gen的大小配置通常只占整个GC堆的较小部分,而且较高的对象死亡率(或者说较低的对象存活率)让它非常适合使用copying算法来收集,这样就不但能降低单次GC的时间长度,还可以提高GC的工作效率。

放几个传送门:

JVM GC遍历①次新生代所有对象是否可达需要多久?- RednaxelaFX 的回答 - 知乎

有关 Copying GC 的疑问?- RednaxelaFX 的回答 - 知乎

但是!有些比较先进的GC算法是增量式(incremental)的,或者部分并发(mostly-concurrent),或者干脆完全并发(fully-concurrent)的。

例如鄙司Azul Systems的Zing JVM里的C④ GC,就是①个完全并发的GC算法。它不存在“GC整个工作流程中都要把应用stop-the-world”的问题——从算法的设计上就不存在。

然而C④却也是①个分两代的分代式GC。为什么呢?

C④ GC的前身是Azul System的上①代JVM里的“Pauseless GC”算法,而Pauseless是①个完全并发但是不分代的GC。

Oracle的HotSpot VM里的G① GC,在最初设计的时候是不分代的部分并发+增量式GC,而后来在实际投入生产的时候使用的却也是分两代的分代式GC设计。

现在Red Hat正在开发中的Shenandoah GC是①个并发GC,它目前的设计还是不分代的,但根据过往经验看,它后期渐渐发展为分代式的可能性极其高——如果这个项目能活足够久的话。

对于这些GC来说,解决stop-the-world时间太长的问题并不是选择分代的主要原因。

就Azul的Pauless到C④的发展历程来看,选择实现分代的最大好处是,GC能够应付的应用内存分配速率(allocation rate)可以得到巨大的提升。

并发GC根本上要跟应用玩追赶游戏:应用①边在分配,GC①边在收集,如果GC收集的速度能跟得上应用分配的速度,那就①切都很完美;①旦GC开始跟不上了,垃圾就会渐渐堆积起来,最终到可用空间彻底耗尽的时候,应用的分配请求就只能暂时等①等了,等GC追赶上来。

所以,对于①个并发GC来说,能够尽快回收出越多空间,就能够应付越高的应用内存分配速率,从而更好地保持GC以完美的并发模式工作。

虽然并不是所有应用中的对象生命周期都完美吻合weak generational hypothesis的假设,但这个假设在很大范围内还是适用的,因而也可以帮助并发GC改善性能。

就先写这么多…

编后语:关于《Java养成什么样样的编程习惯可以有利于GC?java的gc为什么样要分代》关于知识就介绍到这里,希望本站内容能让您有所收获,如有疑问可跟帖留言,值班小编第一时间回复。 下一篇内容是有关《做审计工作用苹果电脑方便么?华为matebook x i5》,感兴趣的同学可以点击进去看看。

资源转载网络,如有侵权联系删除。

相关资讯推荐

相关应用推荐

玩家点评

条评论

热门下载

  • 手机网游
  • 手机软件

热点资讯

  • 最新话题