JVM垃圾收集算法
在介绍GC算法之前,要先了解一下对象在JVM中是如何进行内存分配的。对象所需的内存大小在类加载完成后便可以确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。
如果Java堆是绝对规整的,使用“指针碰撞”的分配方式。所有已经被占用的内存都放在一边,空闲的内存放在另一边,两块区域之间用一个指针作为分界点的指示器,每当需要进行内存分配时,将指针向空闲的内存区域挪动与对象大小相同的距离。
如果Java堆不是规整的,使用“空闲列表”的方式进行分配。JVM中维护着一个列表,记录着哪些内存块是可用的,在分配内存时,在列表中找一块足够大的空间划分给对象实例,并更新列表中的记录。
知道了对象是如何在堆中进行内存分配之后,我们来看一下Java堆中都用都哪些收集算法来保证堆中内存分配的有效应用。
标记-清除算法
标记-清除算法:是最基础的收集算法,存在不足,后续的收集算法都是基于这种思想并对其不足进行改进而产生。分为“标记”和“清除”两个阶段,首先标记出需要回收的对象(关于对象何时应该被回收可以查看我的另一篇博客(JVM中对象的回收判定)),在标记完成后统一回收所有被标记的对象。
不足:
一、标记和清除的效率都不高。
二、由于对象进行分配后指定的内存空间不可变,在标记清除后会产生大量的不连续的内存碎片,大量零散的内存空间会导致需要分配一个较大对象时,因找不到合适的连续的内存空间而触发一次GC操作。
复制算法
复制算法:将内存按容量划分为大小相等的两块,每次只使用其中的一块进行内存分配。当这一块内存不足时,将还存活的对象按顺序分配到另一块内存中,然后再将之前以使用过的内存空间全部清理掉。
这种算法虽然处理了内存碎片的问题,但是实际可用内存缩小到了原来的一半。
大部分商业虚拟机采用这种算法来回收新生代。由于新生代中的对象98%是无法长期存在的,在HotSpot中,将内存分为三块,一块较大的Eden空间和两块较小的Survivor空间,空间比例大小为8:1:1。每次回收时,将Eden和其中一块Survivor空间中还存活的对象复制到另一块Survivor空间中,并清除Eden和刚使用过的Survivor空间。当另一块Survivor空间提供不了足够内存存放存活的对象时,需要依赖老年代进行分配担保,即将存活下来的对象进入老年代。
标记-整理算法
标记-整理算法:标记过程与标记-清除算法相同,在标记完成后将所有存活的对象移动到内存空间的一端,然后清除端边界以外的内存。
这是根据老年代对象存活率较高而产生的算法,由于没有额外的内存空间为老年代进行分配担保,因此老年代无法使用复制算法。1
《深入理解Java虚拟机》 ↩︎