在jvm的新生代内存中,为什么除了Eden区,还要设置两个survivor区?
设置survivor的意义?
假设没有survivor,eden区每进行一次Minor GC,存活的对象就会被送到老年代,老年代很快就被填满,触发major GC, (major GC一般伴随着Minor GC,可以看做触发Full GC)老年代的内存空间远大于新生代,Full GC消耗的时间比Minor GC长得多,频发的Full GC消耗的时间是非常可观的,这会影响大型程序的执行和响应速度,更不要说某些连接会因为超时发生连接错误了。那么在没有survivor的情况下,怎么避免上述情况?
方案 | 优点 | 缺点 |
---|---|---|
增加老年代空间 | 更多存活对象才能填满老年代。降低Full GC频率 | 随着老年代空间加大,一旦发生Full GC,执行所需要的时间更长 |
减少老年代空间 | Full GC所需时间减少 | 老年代很快被存活对象填满,Full GC频率增加 |
显然,没有survivor的话,上面两种方案都不能从根本上解决问题。
这也是survivor存在的意义,减少被送到老年代的对象,进而减少Full GC的发生,survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代
那么为什么需要两个survivor呢?一个不行吗?
假设只有一个survivor区,刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中存活的对象会被移动到survivor区,如此循环下去,下一次Eden区满了,进行Minor GC, Eden和Survivor区各有一些存活对象,如果此时把Eden区的存活对象硬放到survivor区,很明显,这两部分对象所占有的内存是不连续的,也就导致了内存碎片化,
碎片化给java程序性能带来很大的风险,堆空间被散布的对象占据不连续的内存,对内存是很大的浪费。 对此,应该建立两块survivor区,刚刚新建的对象在Eden区域,经历一次Minor GC,Eden中存活对象就会被移动到第一块survivor S0, Eden区被清空, 等Eden区再满了,再次触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor s1,S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中
这样的好处是避免了内存碎片的产生,加快复制效率,不好的地方是,有一块survivor区始终保持空闲,内存利用率低。 那为什么不是三个四个survivor呢? 没有必要,多余的survivor不会有什么作用,
不知道大家有没有想过为什么不能只有eden区,对象在eden区进行标记整理回收垃圾, 楼主认为理论上完全是可以的, 但是由于java中对象有朝生夕死的特点,90%的对象很快就会死亡,仅仅少数对象存活,对于这种情况采用复制算法进行垃圾回收最为高效,仅仅在内存中复制即可,对象少,复制效率高, 试想如果每次都存活很多对象,复制效率就低了