千家信息网

JVM的基础中什么新生代不用标记清除算法

发表于:2024-11-11 作者:千家信息网编辑
千家信息网最后更新 2024年11月11日,JVM的基础中什么新生代不用标记清除算法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。新生代又分为Eden区和Survivor区,S
千家信息网最后更新 2024年11月11日JVM的基础中什么新生代不用标记清除算法

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区域也会存活的对象需要被回收,对Survivor区要采用标记整理垃圾收集算法,(先标记需要清理的对象,然后回收,然后把剩下的存活对象放到一起);

  • Eden区域采用复制算法,把Eden区域存放的对象复制到Survivor区域,然后把整个Eden区清除。

看到网上有些文章说这里设置二个Survivor区域的原因是为了避免内存碎片,因为他假设第二次(以及后续)的回收,内存回收是先回收Eden区域,然后是Survivor区域,这样当然会有内存碎片,但是如果真是只有一个Survivor区域,垃圾回收设计者肯定是先回收Survivor区域,再回收Eden区域,等Survivor区回收整理好,再把Eden区存放对象搬到Survivor区,这样存活地址是连续的,没有内存碎片。所以真正的原因还是我下面说的效率问题。

面试官 :这样有什么问题呢?

我:这样做有几个问题:

  1. 经过几次回收之后,Survivor区域满了之后怎么办?直接搬到老年代?那老年代很快就爆炸了。搬到Eden区?那内存碎片产生了,可能Survivor区和Eden区回收完之后,还需要再整理一下内存去掉内存碎片,性能消耗也是很大的。

  2. 一般标记整理算法的性能消耗是比复制算法消耗要大的,尤其是在新生代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区)不存东西)。

标记复制算法流程:

  1. Eden区域+Survivor From区满,进行存活对象标记,标记完,把存活对象复制到Survivor To区域;

  2. Survivor To区域变成From区域(一个逻辑标识),From区域变成To区域;

  3. 内存分配,继续步骤1,复制过程中有达到老年代晋升年龄(默认值15),移动到老年代。

关于JVM的基础中什么新生代不用标记清除算法问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注行业资讯频道了解更多相关知识。

0