JVM的基础中什么新生代不用标记清除算法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
新生代又分为Eden区和Survivor区,Survivor由From区域和To区域组成,完整的内存结构,我给你画一下,别抽了,笔递给我一下,我画一下,如下图所示。
面试官 :哦,图可以,那为什么堆要分新生代和老年代呢?
我:当然是为了更有效的管理内存。
面试官 :怎么说?
我:假设一下,如果不分新老代,内存就一整块,垃圾收集器每次都要把那些长期存在的对象,和生命周期很短的对象放在一起回收,一般长生命周期的对象可能跟应用生命周期一致,你基本回收不掉的,比如Spring 框架里面的Bean管理相关的对象(ApplicationContext),整个应用运行期间都存在,这种一般经过几次回收最后都放在老年代,但是如果不区分新老代,每次都一起回收,性能消耗很大。
区分新老代之后,老年代放长期存活的对象,新生代就放生命周期短的对象,老年代对象很稳定,新生代回收不影响老年代,回收效率能大大提高。
面试官 :那为什么新生代还要分Eden、From、To区域呢?
我:[开始慢慢有点意思了]
首先大部分对象生命周期是很短的,如果新生代不分多个区域,新生代可能会有二种回收方案
第一种可能:每次回收都在新生代整块内存上进行,完整的垃圾回收过程分三步:
需要先找到需要清理的对象标记;
清理这些被标记的对象;
移动剩下的对象,对达到老年代晋升年龄的对象移动到老年代。
对象被回收掉后会产生很多内存碎片(被回收的对象很多),如果要解决内存碎片,需要移动剩下的对象(标记整理算法),整个回收流程效率很低。
第二种可能:如果没有Survivor区(From + To),Minor GC(新生代回收)过程中,存活的对象直接被送到老年代,这样的话老年代很快被填满,触发Major GC(因为Major GC一般伴随着Minor GC,也可以看做触发了Full GC),Full GC频繁会影响程序的执行和响应速度。
新生代的回收叫Minor GC, 老年代的回收叫Major GC。
面试官 :为什么要设置两个Survivor区呢?From 和 To
我:我们来看一下, 如果只有一个Survivor区,新生代内存的回收流程。
我按照上面这张图画的讲,第一次Eden区域满了,内存回收很简单,直接把Eden区域存活对象放到Suvivor区域;
第二次内存回收,需要回收二个地方,Eden区域和Survivor区域。
看到网上有些文章说这里设置二个Survivor区域的原因是为了避免内存碎片,因为他假设第二次(以及后续)的回收,内存回收是先回收Eden区域,然后是Survivor区域,这样当然会有内存碎片,但是如果真是只有一个Survivor区域,垃圾回收设计者肯定是先回收Survivor区域,再回收Eden区域,等Survivor区回收整理好,再把Eden区存放对象搬到Survivor区,这样存活地址是连续的,没有内存碎片。所以真正的原因还是我下面说的效率问题。
面试官 :这样有什么问题呢?
我:这样做有几个问题:
经过几次回收之后,Survivor区域满了之后怎么办?直接搬到老年代?那老年代很快就爆炸了。搬到Eden区?那内存碎片产生了,可能Survivor区和Eden区回收完之后,还需要再整理一下内存去掉内存碎片,性能消耗也是很大的。
一般标记整理算法的性能消耗是比复制算法消耗要大的,尤其是在新生代98%的对象都是“朝生夕死”的,标记清楚的是98%的对象,剩下就2%对象,要整理内存,不然直接把这2%对象放到另一个地方,把整块内存清除,Eden整块内存清除效率很高的。
所以归根结底,二个Survivor区还是为了性能考虑,标记复制算法效率比标记整理效率高。
面试官 :那你跟我详细讲讲标记新生代除了Eden,另外采用二个Survivor区的标记复制算法。
安琪拉:新生代中的对象 98% 是“ 朝生夕死” 的, 所以并不需要按照 1: 1 的比例来划分Eden和Survivor的空间, 而是将新生代分为较大的一块Eden空间和两块较小的Survivor 空间,每次只使用 Eden 和 其中一块Survivor[0](From区域),留出Survivor[1](To区域)用来实现标记复制。
当回收时, 将 Eden 和 Survivor[0] 中还存活着的对象一次性地复制到另外一块 Survivor[1] (To)空间上, 最后清理掉 Eden 和 刚才用过的 Survivor 空间。
另外说明一点:From区域和To区域在每次Minor GC之后都会互转,From区域变成To区域,To区域变成From区域,这只是逻辑标识
HotSpot 虚拟机默认 将Eden 和 Survivor 的大小比例是 8: 1(CMS不适用), 也就是每次新生代中可用内存空间为整个新生代容量的 90%( 80%+ 10%),只有10%的内存会被“ 浪费”(一直有10%的内存(Survivor To区)不存东西)。
标记复制算法流程:
Eden区域+Survivor From区满,进行存活对象标记,标记完,把存活对象复制到Survivor To区域;
Survivor To区域变成From区域(一个逻辑标识),From区域变成To区域;
内存分配,继续步骤1,复制过程中有达到老年代晋升年龄(默认值15),移动到老年代。
关于JVM的基础中什么新生代不用标记清除算法问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注天达云行业资讯频道了解更多相关知识。