【JVM】垃圾分代回收
【JVM】垃圾分代回收
JVM在回收垃圾的时候,并没有单独采用标记清除、标记整理、复制算法中的某一种,而是结合这三种算法,针对不同的场景使用不同的垃圾回收算法,这种方法在JVM中叫做分代垃圾回收。
分代垃圾回收机制是将堆内存分为新生代和老年代两个部分,如下图。
在新生代中又分为Eden伊甸园区
和Survivor幸存区
,幸存区又分为From
和To
区(还记得垃圾回收算法中的复制算法吗?是不是也有From
和To
两个区?)。
为什么要这么设计呢?为什么叫新生代和老年代呢?想想实际开发中那些Java对象,是不是有些对象用完了就释放了,体现了朝生夕死的特点,有些对象存活时间则比较长。其实JVM的分代设计就是契合这样的场景的。
那么分代回收是如何工作的呢?
新创建的对象首先放入到Eden伊甸园区
(如果新对象过大,会直接分配在老年代中),随着对象的增多并且伊甸园内存是有限的,最后可能无法为对象分配内存空间,就像下图中,想给对象o4
放入到绿色部分,很明显绿色部分不够容纳对象o4
,此时就会触发GC垃圾回收,在新生代的垃圾回收通常叫做Minor GC
,此时会采用复制算法(为什么采用复制算法呢?这是因为新生代中的对象大部分都会死亡,只有少部分存活下来,所以只需要复制少部分对象即可,效率很高。),先标记可达对象,然后将存活对象复制到幸存区中的To
区(为什么是To
区,而不是From
区呢?记住一点To
区永远是空的),此时对象的年龄要加一(对象年龄存储在对象头中,4bit
存储,最大存储的正数也就是15)。
发生Minor GC
后的图如下(假设只有对象o1
存活了):
此时存活的对象o1
会放到To
区,新创建的对象o4
会放入Eden区,此时From
和To
要交换位置(这里From
和To
是逻辑概念,其实就是两个指针而已),如上图所示。
本次Minor GC
完成后,对象依然会继续创建,那么依然会放入到伊甸园区,如果满了,则会触发第二次的Minor GC
,特别要说明的是,此时并非伊甸园区会发生GC
,FROM幸存区
也是可能发生GC
的(因为可能此时TO
区里面的对象就不可达了,也就是没用了),那么就出现了Eden伊甸园区存活的对象和FROM幸存区
中存活的对象都会移动到To
区,此时Eden中复制过来的对象年龄从0变为1,而从From
区到To
区中的对象年龄要加1(发生两次Minor GC
都存活了,所以年龄=2)。假设Eden伊甸园区存活了的对象是o5
,From
区中的o1
对象也存货了下来,那么区域情况如下图所示:
以此类推,每次发生Minor GC
的时候都会将存活的对象放入To
区,并且分代年龄加一,当年龄达到15(默认15,可配置)之后,就会将对象移动到老年代,比如o1
对象达到了的分代年龄达到了15,例如下图所示:
假设此时伊甸园区所有对象都可回收,那么发生Minor GC
的时候,伊甸园区会被清空,幸存区中年龄为15的对象o1
就会移动到老年代,而对象o5
会转移到另外一个幸存区中,此时内存情况如下图所示:
当创建对象的时候,如果新生代伊甸园区无法给他分配足够大的内存,尝试放入老年代,但是老年代也挺满的了,此时的会首先发生Minor GC
行为,释放新生代内存空间,如果回收后依然无法放入新创建的对象,继而对老年代执行GC
,这种老年代的GC
被称为Major GC
或者叫做Full GC
。
如果发生Major GC
,但是依然未能释放很多内存供系统使用,那么系统就会变得缓慢,为什么会缓慢呢?
首先GC
的发生是会导致STW
的,也就是Stop The World
,此时用户线程会被停止,等垃圾回收线程执行完垃圾回收工作后,用户线程才能恢复。
总结
- 创建对象会首先尝试放入新生代中的伊甸园区。
- 伊甸园内存不足的时候会触发
Minor GC
,特别注意Minor GC
的触发条件是伊甸园区内存不足,幸存区内存不足不会触发Minor GC
行为。 - 发生
Minor GC
的时候,伊甸园内的存货对象会复制到幸存区To
中,GC
年龄加一然后交换From
和To
区。 - 当对象的
GC
年龄达到最大值(默认15)的时候,下次GC
会将其放入到老年代。 - 当老年代内存不足的时候,会首先触发
Minor GC
,如果Minor GC
后还是内存不足,则会触发Major GC
。 - 无论是
Minor GC
还是Major GC
都会触发STW
。 Major GC
的发生频率比Minor GC
低,但是时间上Major GC
更长,所以如果出现大量的Major GC
,会导致STW
时间过长,那么用户响应时间就长,系统就出现了问题。