JVM内存管理——GC算法


发布于 2019-09-01 / 37 阅读 / 0 评论 /
JVM内存区域垃圾内存回收算法

1.垃圾内存判定算法

JVM在运行过程中,需要对已死去的实例占用的内存进行回收。首要问题是判断实例是否“死去”。主要有两种算法——引用计数算法和可达性分析算法。

1.1.引用计数算法

实例被引用,计数器加1;引用被取消,计数器减1。

引用计数算法有局限性,例如:实例a和b都有字段c,且a.c=b、b.c=a,如果只是a与b互相引用,则a和b都应该被回收,但引用计数算法无法对此种实例进行回收。

1.2.可达性分析算法

以GC Roots的对象作为起点往下搜索,当一个对象不在GC Roots的任意一条链路中时,则标记此对象“死去”,并进行回收。

GC Roots可以是:虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象。

确认死去的实例后,就需要对这些实例占用的内存进行回收。回收算法主要有以下三种:标记-清除算法、复制算法、标记-整理算法、分代收集算法。

2.垃圾回收算法

根据使用场景不同,可有以下四种算法。

2.1.标记-清除算法

先对需要回收的对象进行标记,然后统一回收。如下图所示:

标记-清楚的缺点也是显而易见:清除之后的内存区域不连续,碎片太多。而且标记和清楚的效率都不高。

2.2.复制算法

将可用内存按容量分为大小相等的两份,每次只使用其中的一块,当这一块用完之后,把存活的对象复制到另一块,并把使用完了的进行清理。此算法实现简单,运行高效。如下图所示。

由上图可看出,实现复制算法需要保留一般的内存,极大地降低了内存的使用效率。考虑到新生代有98%的实例都是朝生夕灭,如果用复制算法来回收新生代的话,就可以减少保留区内存占用的比率。现在的商业虚拟机都是采用这种算法来回收新生代,一般把新生代分为Eden区和两个Survivor区,GC前,其中一个Survivor区作为保留区,保存每一次GC后还存活的对象,GC后此Survivor与Eden区组成可用空间,另一个Survivor就成为了保留区。

配置SurvivorRatio参数,可以改变Eden区和Survivor区内存大小比例,默认为8:1,也就是说新生代中Eden占80%,From Survivor占10%,To Survivor占10%,有10%的空间会被浪费。提升了复制算法的内存利用率。

2.3.标记-整理算法

所有存活的对象都向一端移动,直接清理端边界以外的内存。如下图所示:

这样,就解决了内存碎片化的问题。

2.4.分代收集算法

根据对象的存活时间来选择收集算法。

(1)一般新生代存活率低,使用复制算法。

(2)老年代存活率高、没有额外空间进行担保,使用“标记-清理”或者“标记-整理”算法。

更多关于垃圾回收问题,可参考https://docs.oracle.com/en/java/javase/12/gctuning/index.html。