分代回收

【JVM】垃圾分代回收

JVM在回收垃圾的时候,并没有单独采用标记清除标记整理复制算法中的某一种,而是结合这三种算法,针对不同的场景使用不同的垃圾回收算法,这种方法在JVM中叫做分代垃圾回收

分代垃圾回收机制是将堆内存分为新生代和老年代两个部分,如下图。

jvm分代

在新生代中又分为Eden伊甸园区Survivor幸存区,幸存区又分为FromTo区(还记得垃圾回收算法中的复制算法吗?是不是也有FromTo两个区?)。

为什么要这么设计呢?为什么叫新生代和老年代呢?想想实际开发中那些Java对象,是不是有些对象用完了就释放了,体现了朝生夕死的特点,有些对象存活时间则比较长。其实JVM的分代设计就是契合这样的场景的。

那么分代回收是如何工作的呢?

新创建的对象首先放入到Eden伊甸园区(如果新对象过大,会直接分配在老年代中),随着对象的增多并且伊甸园内存是有限的,最后可能无法为对象分配内存空间,就像下图中,想给对象o4放入到绿色部分,很明显绿色部分不够容纳对象o4,此时就会触发GC垃圾回收,在新生代的垃圾回收通常叫做Minor GC,此时会采用复制算法(为什么采用复制算法呢?这是因为新生代中的对象大部分都会死亡,只有少部分存活下来,所以只需要复制少部分对象即可,效率很高。),先标记可达对象,然后将存活对象复制到幸存区中的To区(为什么是To区,而不是From区呢?记住一点To区永远是空的),此时对象的年龄要加一(对象年龄存储在对象头中,4bit存储,最大存储的正数也就是15)。

伊甸园区慢慢满了

发生Minor GC后的图如下(假设只有对象o1存活了):

minor gc1

此时存活的对象o1会放到To区,新创建的对象o4会放入Eden区,此时FromTo要交换位置(这里FromTo是逻辑概念,其实就是两个指针而已),如上图所示。

本次Minor GC完成后,对象依然会继续创建,那么依然会放入到伊甸园区,如果满了,则会触发第二次的Minor GC,特别要说明的是,此时并非伊甸园区会发生GCFROM幸存区也是可能发生GC的(因为可能此时TO区里面的对象就不可达了,也就是没用了),那么就出现了Eden伊甸园区存活的对象和FROM幸存区中存活的对象都会移动到To区,此时Eden中复制过来的对象年龄从0变为1,而从From区到To区中的对象年龄要加1(发生两次Minor GC都存活了,所以年龄=2)。假设Eden伊甸园区存活了的对象是o5From区中的o1对象也存货了下来,那么区域情况如下图所示:

第二次MinorGC

以此类推,每次发生Minor GC的时候都会将存活的对象放入To区,并且分代年龄加一,当年龄达到15(默认15,可配置)之后,就会将对象移动到老年代,比如o1对象达到了的分代年龄达到了15,例如下图所示:

gc-达到分代年龄15.drawio

假设此时伊甸园区所有对象都可回收,那么发生Minor GC的时候,伊甸园区会被清空,幸存区中年龄为15的对象o1就会移动到老年代,而对象o5会转移到另外一个幸存区中,此时内存情况如下图所示:

gc-达到分代年龄15-2.drawio

当创建对象的时候,如果新生代伊甸园区无法给他分配足够大的内存,尝试放入老年代,但是老年代也挺满的了,此时的会首先发生Minor GC行为,释放新生代内存空间,如果回收后依然无法放入新创建的对象,继而对老年代执行GC,这种老年代的GC被称为Major GC或者叫做Full GC

如果发生Major GC,但是依然未能释放很多内存供系统使用,那么系统就会变得缓慢,为什么会缓慢呢?

首先GC的发生是会导致STW的,也就是Stop The World,此时用户线程会被停止,等垃圾回收线程执行完垃圾回收工作后,用户线程才能恢复。

总结

  1. 创建对象会首先尝试放入新生代中的伊甸园区。
  2. 伊甸园内存不足的时候会触发Minor GC,特别注意Minor GC的触发条件是伊甸园区内存不足,幸存区内存不足不会触发Minor GC行为。
  3. 发生Minor GC的时候,伊甸园内的存货对象会复制到幸存区To中,GC年龄加一然后交换FromTo区。
  4. 当对象的GC年龄达到最大值(默认15)的时候,下次GC会将其放入到老年代。
  5. 当老年代内存不足的时候,会首先触发Minor GC,如果Minor GC后还是内存不足,则会触发Major GC
  6. 无论是Minor GC还是Major GC都会触发STW
  7. Major GC的发生频率比Minor GC低,但是时间上Major GC更长,所以如果出现大量的Major GC,会导致STW时间过长,那么用户响应时间就长,系统就出现了问题。

标签: 分代垃圾回收

添加新评论