为什么HotspotJVM垃圾回收中的“标记-复制”算法需要两个survivor空间?

一、提出问题

如题,为什么HotspotJVM垃圾回收中的“标记-复制”算法需要两个survivor空间?为什么要强调Hotspot JVM呢,因为JVM有很多种,每种JVM的实现方式都不一样。本文提到的JVM,一律是代表Hotspot JVM。

二、背景

熟悉jvm的童鞋,都应该了解到,一些经典的新生代垃圾收集器运用了“标记-复制算法”,并且,为了较好的实现复制算法,通常把新生代分为两种逻辑分区,一种叫eden空间,另一种叫survivor空间。jvm给一个对象分配内存时,会优先分配到eden空间;然后survivor,顾名思义就是从eden空间经历过Minor GC但仍“得以生存”的那些对象存储的内存空间。
那按照jvm实现的思路,为什么需要两个survivor空间呢?
这个问题用反证法比较好理解,我们作以下几个假设。
在这里插入图片描述

三、假设

这里设置新生代被分配的内存空间是10,作以下假设:

  1. 假设没有survivor空间,eden空间和survivor空间大小比是10:0。 这个假设有点不攻自破,因为既然是复制算法,肯定是至少有一个原来的内存空间,以及一个要复制到的目标空间,所以内存只有一个逻辑分区(这里指eden空间)是不够的,至少要两个逻辑分区,才能实现“得救者”的转移。
  2. 假设只有一个survivor,eden空间和survivor空间大小比是5:5。 这样一来,把新生代内存分成两个空间,eden和surivor各占一半。我们可以想象一下,jvm会在eden空间为新对象分配内存,然后到MinorGC发生时,eden空间中得以存活的对象被复制并移动到survivor空间(假设survivor空间足够大),然后把eden空间清空,eden空间和survivor空间角色互换,准备下一次新的对象空间的分配。这个过程看起来毫无破绽,但细心点看,可以发现,每次可分配的内存空间大小只有新生代空间的一半,这未免也太浪费了吧?
    根据IBM一项专门的研究表明:新生代的对象有98%是”朝生夕灭“,什么概念呢?也就是说很多对象都熬不过第一次MinorGC。所以,如果eden空间和survivor空间设置成一样大小的两块,那么也许会看到这么一种现象:Eden空间很满,而survivor空间几乎是空的。这样也可能引发严重的问题,就是如果来了一个超大对象(比eden空间还大),由于eden空间被排挤得太小,而不得不把这个超大对象提前晋升到老年代,而造成老年代的空间瞬间膨胀,可能会引发FullGC,从而导致用户程序卡顿。
    在这里插入图片描述
  3. 假设只有一个survivor,eden空间和survivor空间大小比是9:1。 再来看看survivor空间只有一个的另外一个情况,这次是把survivor缩小到是eden的1/9。因为新生代只划分了两个空间,所以发生MinorGC的处理方式跟上一条是一模一样的,但到了最后一步,因为eden和survivor的空间大小是9:1,之后如果发生角色互换,则eden和survivor大小比是1:9,这对对象的内存分配来说,是无法接受的,所以这种情况也是不妥的。

四、结论

经过以上的几种假设,我们可以得出结论。如果eden空间和survivor空间设置成一样的大小,是可以实现“半区复制”的,但这种做法来划分jvm的新生代是不合理的。正确的做法我们都知道了:eden空间占绝大部分,再设置两个相同大小的from survivor空间和to survivor空间,三者默认大小比是8:1:1,发生MinorGC时,eden和from survivor中被判断存活的对象被移动到to survivor,剩下的内存空间都被回收,然后from survivor和to survivor空间互换,此时的eden和from survivor都是被清空了的,准备下一轮的对象分配和GC 而控制这个比例的参数是-XX:+SurvivorRatio,如-XX:SurvivorRatio=8代表eden空间大小占8,另外两个survivor空间各占1。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章