1、标记-清除算法
如同它的名字一样,该算法分为“标记”和“清除”两个阶段,它的标记过程就是我们在堆中的引用计数算法,主要存在两个不足
(1)效率问题,标记和清除两个过程的效率都不高
(2)空间问题,标记清除之后会产生大量的不连续的内存空间,会导致以后有大的对象时,无法找到足够的连续内存而不得不提前触发一次垃圾回收。
2、复制算法
为了解决标记清除算法中的效率问题,出现了“复制”算法,它将内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就将还存活的对象复制到一块内存中,然后把已使用的这一块一次清理掉。这样就不用考虑内存碎片的问题,只要移动堆顶指针,按顺序分配内存即可。
IBM又将此算法做了进一步的优化,因为在新生代中大约98%的内存都是要被回收的,把内存对等分配会造成资源上的浪费,因此将内存按照8(Eden):1(Survivor):1(Survivor)分成了三份,每次使用其中的两个共90%的空间,在回收时将Eden和Survivor中存活的对象复制到另一个Survivor空间中,然后清除这两块空间,这样就大大减少了资源的浪费,但是我们没有办法保证每次回收都只有不多于10%的对象存活,当survivor空间不够是,需要依赖其他内存(老年代)进行担保
3、标记-整理算法
复制算法适合对象存活率不高的垃圾回收,当对象的存活率高时就会降低效率,对于这种情况,又引出了标记-清除算法,可以分为两个过程。
(1)首先标记哪些对象存活,把存活的对象向一端移动
(2)然后将最后一个存活对象之后的空间全部回收
4、分代收集算法
这种算法根据对象的存活周期的不同将内存划分了几块,java中一般分为新生代和老年代,
然后根据年代的不同使用不同的算法对对象进行收集清理。
在新生代中,以为每次垃圾收集时都会有大量的对象死去,只有少量的存活,使用复制算法可以提升收集的效率。
老年代中由于对象的存活率高,没有额外的空间担保,就使用“标记-清理”或者“标记-整理”算法来回收
一个对象怎么划分新生代和老年代呢?
1.大对象:所谓的大对象是指需要大量连续内存空间的java对象,最典型的大对象就是那种很长的字符串以及数组,大对象对虚拟机的内存分配就是坏消息,尤其是一些朝生夕灭的短命大对象,写程序时应避免。
2.长期存活的对象:虚拟机给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1,。对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当他的年龄增加到一定程度(默认是15岁), 就将会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
3.动态对象年龄判定:为了能更好地适应不同程度的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。