1、为什么要对堆内存分代
我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的,如果分代的话,我们把新创建的对象放到某一地方,当GC的时候先把这块存“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。
2、年轻代
HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1,为啥默认会是这个比例,接下来我们会聊到。一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区。对象在Survivor区中每熬过一次Minor GC(young GC),年龄就会增加1岁,当它的年龄增加到一定程度(15岁)时,就会被移动到年老代中。
因为年轻代中的对象基本都是朝生夕死的(80%以上),所以在年轻代的垃圾回收算法使用的是复制整理算法,复制整理算法的基本思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。
复制整理算法
- 优点:不会产生内存碎片。
- 缺点:(1)会开辟新的空间也就是 To survivor,用来保存有用对象(2)复制对象会花费一些时间
在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
3、老年代
当年轻带随着不断地Minor GC ,from survivor中的对象会不断成长,当from survivor中的对象成长大15岁的时候,就会进入老年代,随着Minor GC的持续进行,老年代中对象也会持续增长,最终老年代的空间也会不够用,此时就会执行老年代的GC-->Major GC。Major GC使用的算法是标记清除算法或者标记-压缩算法。
标记清除:(1)首先会去根对象的集合中进行遍历,发现对象还存在引用,就是存活的对象并打上一个存活的标记(2)再去根对象集合进行二次遍历,将没有被打上标记的对象清除掉。(将所有对象扫描2次比较消耗时间)
优点:能够进入老年代的对象,一般都相对稳定,所有被回收的数量较少,减少对磁盘的清理,如果采用复制整理算法,被复制的对象会有很多。
缺点:虽然垃圾得到了回收,但是回收以后,堆内存中出现了不连续的现状---内存碎片,导致大对象无法创建
标记压缩:和标记清除算法基本相同,唯一不同的就是,在清除完成之后,会把存活的对象向内存的一边进行压缩,这样就可以解决内存碎片问题。
4、有关年轻带的配置参数
1)-XX:NewSize和-XX:MaxNewSize
用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
2)-XX:SurvivorRatio
用于设置Eden和其中一个Survivor的比值,这个值也比较重要。
3)-XX:+PrintTenuringDistribution
这个参数用于显示每次Minor GC时Survivor区中各个年龄段的对象的大小。
4).-XX:InitialTenuringThreshol和-XX:MaxTenuringThreshold
用于设置晋升到老年代的对象年龄的最小值和最大值,每个对象在坚持过一次Minor GC之后,年龄就加1。