Java的GC垃圾回收算法
GC如何判断一个对象为“垃圾”的
Java堆内存中存放着几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。那么GC具体可以通过哪些手段来判断一个对象已经“死去”的
1、引用计数算法
该算法通过判断对象的引用数量来决定对象是否可以被回收,在此机制下,堆中的每个对象都存在一个引用计数器,当一个对象创建后被分配一个引用之后则对应的引用计数器+1,当引用完成后则引用计数器响应的-1,当某个对象的引用计数器为0的时候,则会被虚拟机作为垃圾进行回收,并释放其锁占用的内存空间。
优点:引用计数算法的执行效率高,程序执行所受的影响较小。
缺点:无法检测出循环引用(父对象引用子对象,子对象引用父对象),其内存空间无法被回收从而导致内存泄漏。
2、可达性分析算法
该算法是通过判断对象的引用链是否可达来决定对象是否会被作为垃圾进行回收。
该算法将程序的所有引用作为一张图,通过将一些GC Roots对象作为起始点,从这些节点开始向下搜索,搜索所做过的路径被称为引用链,如果某个GC Roots没有与任何引用链相连,则称GC Roots到该对象为不可达,此时该对象可以作为垃圾进行回收,然后释放其所占据的内存空间。
垃圾回收算法
1、标记-清除算法
最基础的垃圾回收算法,分为两个阶段:标记和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
缺点:
- 执行效率不稳定:堆中有大量对象,会进行大量的标记和清除
- 内部的碎片化问题:标记和清除之后会产生大量的不连续的内存碎片
2、复制算法
半区复制的垃圾回收算法
将可用内存按容量划分成大小相等的两块,每次使用其中的一块,当这一块的内存用完了,就将还活着的对象复制到另一块上面,然后再把使用过的内存空间一次性清理掉
优点:不会产生内存碎片
缺点:只有一半内存可用,空间浪费太多
3、标记-整理算法
标记过程仍然使用标记-清除,后续步骤将所有存活的对象都向内存空间一端一移动,然后清理掉边界以外的内存
优点:没有内存碎片的产生
缺点:在每次移动的过程中,尤其是对老年代这种存活对象多的区域,会导致效率的降低
4、分代收集算法
分代收集算法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代和新生代。
老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同的区域选择不同的算法。
目前大部分JVM的GC对于新生代都采用复制算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常不是按照1:1来划分新生代。
一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。
而老生代因为每次都只回收少量对象,因而采用标记-整理算法。
在进行GC后,当对象在Survivor区躲过一次GC后,其年龄就会+1.默认情况下年龄达到15的对象便会被移到老生代中。
