目录
JVM的垃圾回收算法按阶段分为两类:一类是标记阶段的算法,另外一类是清除阶段算法。
标记阶段
垃圾标记阶段主要是判断哪些对象是存活对象,哪些对象是死亡对象,只有被标记为死亡的对象,GC在执行垃圾回收时,才会释放其占用的内存空间。在垃圾标记阶段有两种算法来判断对象是否存活,一种是引用计数算法,另外一种是可达性分析算法。
引用计数算法
对每个对象保存一个整型的引用计数器属性,用于记录对象被引用的次数。比如一个对象被引用了一次,则计数器加1,减少了引用的时计数器减1,当计数器值为0的时候,则表示该对象可以进行回收。java不使用引用计数算法,python使用了引用计数算法。
优点:实现简单,垃圾对象便于识别,判定效率高,回收没有延迟性。
缺点:需要额外的存储空间来存放计数器;存在循环引用的情况,导致对象无法被回收。
可达性分析算法
可达性分析算法是以根对象集合(GC Roots)为起点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连着,搜索所走过的路径称为引用链。如果目标对象没有任何引用链相连,则是不可达,意味着该对象已经死亡,需要回收。如果目标对象能够与引用链相连,则该对象是存活对象。简单理解,可以认为如果对象在引用链上,则表示是存活对象。
GC Roots包括如下几类对象:
1.虚拟机栈中的引用对象,例如方法中的形参,局部变量等。
2.本地方法栈内的引用对象。
3.方法区中的静态属性引用对象,如java类中引用的静态变量。
4.方法区中常量引用的对象,如字符串常量池(String Table)里的引用。
5.所有被同步锁synchronized持有的对象。
6.java虚拟机的内部引用,如基本类型的包装类的Class对象,一些常驻内存的异常(NullPointerException),系统类加载器等。
特点:可达性分析算法的分析工作需要在一个能保障一致性的快照中进行,因此就有了GC时会出现的“Stop The World”的这种情况。
清除阶段
当标记阶段成功区分出了内存中的存活对象和死亡对象后,GC接下来的任务就是执行垃圾的回收,释放死亡对象所占用的内存空间,这个阶段称为清除阶段 。清除阶段主要有3种算法,标记-清除算法(Mark-Sweep),复制算法(Coping),标记-压缩算法(Mark-Compact)。
标记-清除算法
当堆中的有效内存被耗尽时,就会停止整个程序,然后会进行两个工作,先进行标记,再进行清除。标记是从引用根节点开始遍历,标记所有被引用的对象,记为可达对象。清除是把标记阶段没有被记为可达的对象进行回收。
缺点:效率不高,在执行GC的时候,需要停止整个程序,且清理出的内存空间不是连续的空间。
复制算法
将活着的内存空间分为2块,每次只使用1块,在垃圾回收的时候将正在使用内存的存活对象复制到另外一个内存空间,然后清除现在正在使用的内存;下一次垃圾回收的时候也是同理,两块内存相互交换,把正在使用的内存的存活对象复制到另外一个内存空间。新生代的GC使用的就是复制算法。
优点:没有标记和清除过程,实现简单,效率较高;能够清理出连续的内存空间。
缺点:占用内存空间大。对于G1这种分拆成大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,导致内存和时间的开销都比较大。
标记-压缩算法
第一阶段和标记-清除算法的第一阶段的标记一样,从根节点开始标记所有存活的对象。第二阶段是把存活的对象压缩到内存的一端,按顺序排放。然后清理边界之外的所有空间。
优点:内存空间连续;不像复制算法一样占用2倍的内存空间,就是占用内存空间小。
缺点:效率更低,比标记-清除算法的效率都低,毕竟多了把存活对象压缩移动的这一步操作。
分代收集算法和分区算法
分代收集算法:java在G1垃圾收集器之前的算法基本上都可以算作是分代收集算法,分代收集算法是基于java对象的生命周期采用不同的垃圾回收算法,把堆空间分为了新生代和老年代,新生代内存较老年代小,对象生命周期短,回收频繁,适合用复制算法,为了解决2倍内存问题,又设计出了survior区来作为复制区。老年代内存较大,对象生命周期长,不适合复制算法,而采用标记-清除或标记-压缩算法。通过这种方式来提高垃圾回收效率。
分区算法:是把堆分为了一个一个区region,然后在清除的时候按region分区进行垃圾回收,每次只对一部分的region进行垃圾回收,减少STW的影响。