分代收集GC算法

2023-07-28 jvm

JVM采用的就是分代收集算法,它本质上是将3种垃圾收集算法整理起来;

一个运行的系统里面的每一个对象,他们的生命周期是不一样的,大部分是短命小部分是长命。所以不同的年龄对象分开处理,即分代处理,年轻的对象放在年轻代,年老的对象放在老年代。

年轻代和老年代是1:2的关系,年轻代再分为eden: survivor (from:to) = 8:1:1的关系存在.

堆内存分配

# 分代收集算法原理及运行过程

# 1.系统运行后,大部分对象直接进eden

对象进eden

# 2.Eden区满触发ygc,清空Eden

由于大部分对象都进入eden必将导致eden区满,eden满后就触发YoungGC,ygc的过程通过可达性分析算法,把存活对象复制到survivor_ from区,此时from区的对象年龄为1(例如对象a_age=1),然后清空eden区。

分代步骤二

# 3.Eden区再次满触发ygc,判断年龄是否应增长

Eden清空后系统继续运行,过段时间Eden继续被填满,还是继续YoungGC,YoungGC后Eden存活对象继续拷贝到survivor_ from区,这次拷贝过来的对象年龄为1(例如对象b_age=1),同时也对survivor_from区进行可达性分析算法,判断老对象是否存活。

  • 如果没有死亡垃圾对象,之前的a_age=2,新增的b_age=1
  • 如果发现死亡对象,采用复制-清除算法对survivor的from和to区进行互换。

分代步骤三

# 4.年龄到15进入老年代

survitor区达到一定年龄后,进入到老年代,默认是15岁(CMS标记清除算法里默认是6岁)。

分代步骤四.drawio

# 5.老年代满触发FullGC

如果老年代也满了就会触发FullGC,老年代采用的标记整理或标记清除(CMS) 算法,因为老年代的对象一般都是存活期较长的对象,就没必要复制来复制去的。

分代步骤五

总结

由于对象的生命周期不同,才采用了分代存储,短命存年轻代,长命存老年代。

  • 年轻代采用复制算法。
  • 老年代采用标记-清除或标记-整理。

# 思考:什么对象绕过年轻代,直接进入老年代?

在JVM中,通常情况下对象会首先在年轻代进行分配和回收,只有在经过一定的条件判断后才能进入老年代。不过有一种情况例外:

大对象:如果对象的大小超过了年轻代的阈值即超过了一个较大的设定值(例如-XX:PretenureSizeThreshold参数指定的值),则该对象会直接在老年代分配空间。这是为了避免大对象经过多次复制而产生的额外开销。

需要注意的是,对于大对象的绕过和晋升条件的设定可能因不同的JVM实现和版本而有所差异。这些设定参数可以通过JVM的启动参数进行配置和调整。

小结

也就说有两种情况survivor对象会进入老年代,大对象会直接进入,默认年龄15的对象会进入。

# 动态年龄算法

在JVM中年轻代的Survivo区中的对象会根据动态年龄算法进行晋升,该算法主要解决的问题是避免由于年龄计数器导致的频繁操作(每次Eden满都会触发复制算法),从而提高垃圾回收的效率。

动态年龄算法基于一个假设:经过一次垃圾回收后存活的对象,其下一次垃圾回收后仍然存活的概率会很高,假设这个对象存活率很高的话就可以将其放置到老年代。

具体来说,动态年龄算法维护了两个年龄计数器(Age),一个叫做对象年龄计数器(Object Age),用于记录对象经过的垃圾回收次数,另一个叫做晋升年龄数器(Promotion Age),用于记录对象需要达到的年龄才能晋升到老年代。

初始时,所有Survivor区中的对象的年龄都是0,每经过一次垃圾回收对象年龄+1。当对象的年龄达到了晋升年龄计数器的值时(默认15),该对象会被晋升到老年代。

但是,如果在某次垃圾回收中,某个Survivor区的存活对象的大部分都是上一次垃圾回收时存活,即年龄相差不大的情况下,JVM会调整晋升年龄计数器的值,将其调整为这些存活对象的Object Age中的最大值。具体是当累加到的年龄时的总和大于50% (-XX:TargetSurvivorRatio=? 默认值是50),只要比X大的都会晋升到老年代。

举例如图所示,对象年龄从小到大进行累加,当累加到3年龄时的总和=70%>50%,那么比3大的对象都会晋升到老年代。即4岁的15%、5岁的5%晋升到老年代。

动态年龄算法

通过这种动态调整,动态年龄算法可以避免频繁的晋升操作,减少垃圾回收带来的性能影响。

上次更新: 5 个月前