Hot Spot虚拟机新生代为什么是一个eden+2个survivor

注:本文针对Hot Spot虚拟机

一、分代收集

在很多时候,JVM中对象的生命周期差距较大,部分对象可能是“朝生夕死”的(大部分),而部分对象可能又是比较“命长”的(小部分)。所以根据对象生命周期的特点,我们将堆空间分为几个区域,比如新生代、老年代,在不同的分代可以采取不同的收集算法,以最大化效率。

二、新生代与复制算法

不考虑特殊情况,对象会优先分配到新生代,并且对象大多都是朝生夕死的,每次新生代GC都会有大批的对象死去,只有少量存活,所以新生代一般采取“复制算法”来进行垃圾收集。

复制算法的思想就是将内存空间划分为大小相等的两部分,每次只使用其中一块,当这块内存不足以容纳新对象的时候,就将存活的对象复制到另外一块上,然后将前一块内存空间中使用的内存一次性清理掉,这样在内存分配时就不用考虑内存碎片的情况了,既简单又高效。

三、一个eden和一个survivor

既然采取复制算法,那我们就需要把新生代内存空间进行划分。但是在新生代中的对象大多数都是短命的,如果将新生代划分为大小相等的两部分的话,就太浪费内存空间了,所以即使我们需要划分新生代空间,也并不需要按照1:1的标准来。既然大多数对象都是短命的,那么我们按照一个大空间、一个小空间来进行划分就好了嘛,这最好再增加一个可配置参数,让用户可以根据实际情况来进行调整。对象优先分配到大的那块空间,经过一次GC之后,只有少数的对象存活,我们将这些存活的对象复制到小的那块空间,正好合适。

假设我们称大的那块空间叫做eden,小的那块叫做survivor。如果新生代就是这种内存布局,能解决我们的问题吗?

首先对象优先分配到eden,当eden空间不能再容纳新对象的时候,触发一次Mnior GC,将eden中还存活的对象复制到survivor中(假设这会儿survivor放得下),然后直接清理eden中使用过的内存空间,这没有什么问题。

但是如果eden空间再次不足,又触发Minor GC呢?此时survivor中同时存在存活的对象和死亡的对象(需要被清理),并且存活的对象可能还没有到进入老年代的年龄,它们还需要留在新生代。我们需要清理survivor中死亡的对象,保留存活的对象,并且将eden中存活的对象复制到survivor中,然后清理eden。那么该如何操作呢?

首先,我们可以使用标记清理算法来清理survivor中死亡对象所占的内存空间,然后将eden中存活的对象复制到survivor中的空闲位置(现在暂时不用考虑空间不足的情况)。但需要注意的是,此时survivor中的这些空闲空间并不是连续的,也就是说可能会有很多内存碎片,这种情况下在其中分配内存会非常麻烦。

当然,为了避免这种麻烦,我们可以使用标记整理算法,将survivor中所有存活的对象都向一端移动,然后直接清理掉边界以外的内存,这样空闲空间也就连续了,复制eden存活对象的时候就可以直接移动堆顶指针,按序放入,不用再考虑前面内存碎片的问题。但不可避免的,我们多了整理survivor空间的操作。

四、一个eden和两个survivor

考虑到前面描述的一个eden和一个survivor的情况会有较大的性能损耗,我们尝试再划分出一块内存空间来解决这个问题,这块内存空间的作用是什么呢?

GC时,我们可以直接将eden和survivor空间中存活的对象复制到这个新划分的内存空间中,然后清理eden和survivor的内存空间,这时它们的可用内存都规整了,下个周期又转换survivor和这个新空间的角色,周而复始,对象可能会在survivor和新空间中来回移动,而每个对象都有自己的GC年龄,年龄到的时候进入老年代就行了。这样是不是高效多了?

这个新空间也是用于容纳经历过GC还存活的对象,所以也可以称之为survivor,并且并不需要很大的内存空间,我们将其定义为和survivor一样的大小,同时为了作为区分,一个叫做from survivor,一个叫做to survivor,并且它们是等效的。基于上述流程,其中一个survivor始终是空的,假设eden和两个survivor的比列为 8:1:1,那么其实我们也只浪费了10%的新生代空间。

那如果我们再分几块survivor空间,性能会不会更好呢?明显不是的,从我们前面的分析来看,再增加survivor并不能解决什么实际的问题,还会徒增空间维护成本,就有点画蛇添足的味道了。

五、总结

简单的来说,出于性能考虑,分代收集是合理的,同时根据大多数对象短命的特点,新生代采取复制算法同样有利于在实际场景中提高性能。而复制算法的高效,需要至少一块空的内存空间(规整),这样在复制对象的时候才不需要考虑内存碎片的影响,也才能体现其性能。但是新生代如果只使用一块eden和一块survivor,是不能保证没有内存碎片影响的,除非经历过一次GC之后,survivor中存活的对象直接进入老年代,但是这样分代收集又没有什么意义了。

所以说现在这种空间划分,包括分代收集,其实都是出于性能考虑,不这样做其实也能实现功能。当然,性能肯定是越高越好。我们在思考的时候不要太在意每个区域是什么名字,更不要死记硬背,重要的是理解它的工作方式和存在的意义。

注:本文是博主的个人理解,如果有错误的地方,希望大家不吝指出,谢谢

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