垃圾回收算法的演进

614 阅读3分钟

 垃圾回收算法简介

1. 标记清除算法

      随着程序在的运行,不断的申请内存,必然会触发垃圾回收,JVM根据可达性分析算法,来判定哪些对象是可回收的,哪些对象是不可回收的。标记之后,就会对垃圾对象进行清除,我们称其为标记清除算法。

那标记清除算法会带来哪些问题呢?

(1)效率问题,标记和清除的效率都不高;

(2)空间的问题,标记清除以后会产生大量不连续的空间碎片,空间碎片太多可能会导致程序运行过程需要分配较大的对象时候,无法找到足够连续内存而不得不提前触发一次垃圾收集

2. 标记整理算法

      为了解决空间问题,就引入了标记整理算法,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,这样就解决了内存碎片的问题了。

3. 复制清除算法

       标记清除算法虽然解决了空间问题,但依然存在效率问题。此时,我们便引入了复制清除算法,它先将可用的内存按容量划分为大小相同的两块,每次只是用其中的一块。当这块内存用完了,就将还存活着的对象复制到另一块上面,然后把已经使用过的内存空间一次清理掉。

(1)解决了效率问题(是否一定会提升效率呢?为什么老年代不适用复制算法呢?请耐心往下看)

(2)解决了内存碎片问题

(3)引入了内存利用率的问题

JVM中对象的特性

(1)大量存活时间短的对象

(2)少量存活时间长的对象

(3)大对象


根据对象特性,来看JVM堆内存分配比例

      新生代和老年代:因为存在长期存活的对象,也存在存活时间极短的对象,所以我们要分代处理,新生代:老年代 = 1:1。

       Eden区和survivor:由于新生代存在的对象朝生夕灭,所以建议使用复制算法,只需要复制少量的存活对象,所以效率较高。同时为了优化复制算法,所以设计出了两个survivor区,eden:s0:s1 = 8:1:1。此时就优化了内存使用率。

此时,新生代的算法已经确定,使用复制清除算法,那老年代我们是否也可以复用此算法呢?

      答案是否定的,因为新生代的对象朝生熄灭,而老年代的对象是长期存活的,使用需要复制大量内容,效率较低,所以建议采用标记整理算法或者多次标记清除 + 压缩。


PS:在上面一直会提到一个效率,通常没有JVM调优经验的人,都对此概念没有一个认知,举个简单的例子:假如新生代垃圾回收耗时100ms,那么老年代垃圾回收大概就要消耗1s,大概10倍的关系。在执行垃圾回收的时候,会stop the world,假如我们系统暂停100ms,用户的体验是怎样的?系统暂停1s,用户的体验有时怎样的?


看了上面的内容,来回顾一下,还有哪些问题是接下来我们需要思考的呢?

(1)何时触发GC?

(1)哪些对象可以进入老年代?什么时候进入老年代?

(1)新生代的内存不够了,JVM会如何处理?老年代的内存不够了,又该如何处理?

(1)默认的堆内存分配策略是否就真的合理呢?

那么下一篇文章,就围绕着几个话题展开