Java GC机制与常用算法

GC

​ Garbage Collection 垃圾收集:所谓的垃圾是指,在系统运行过程当中所产生的一些无用的对象,这些对象占据着一定的内存空间,如果长期不被释放,可能导致OOM

​ Java中,GC的对象是Java堆和方法区(即永久区) 。在C/C++里是由程序猿自己去申请、管理和释放内存空间,因此没有GC的概念

​ Java中,后台专门有一个专门用于垃圾回收的线程来进行监控、扫描,自动将一些无用的内存进行释放,防止引入认为的内存泄露

GC优化目标

不该回收的对象一定不能回收,该回收的对象尽量回收
尽可能少的暂停应用的运行
CPU消耗尽量低
在时间,空间,回收频率中平衡
衡量标准:吞吐量尽量大,STW尽量小

GC算法

引用计数算法

老牌垃圾回收算法。无法处理循环引用,没有被Java采纳

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的

优点:实现简单,判定效率高

缺点:很难解决对象之间循环引用的问题,对于循环引用的对象无法回收;

引用和去引用伴随加法和减法,影响性能

根搜索算法

设立若干种根对象,当任何一个根对象到某一个对象均不可达时(即使有关联),则认为这个对象是可以被回收的

可达性分析:从根(GC Roots)的对象作为起始点,开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连(用图论的概念来讲,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的

可当做GC roots的对象 (局部变量,静态成员变量,常量)

1、栈(栈帧中的本地变量表)中引用的对象
2、方法区中的静态成员
3、方法区中的常量引用的对象(全局变量)
4、本地方法栈中JNI(一般说的Native方法)引用的对象

根搜索算法的扩展:标记-清除算法、复制算法、标记-整理算法

标记-清除算法

两个阶段:标记阶段和清除阶段

标记阶段:通过根节点,标记所有从根节点开始的可达对象

清除阶段:清除所有未被标记的对象
Java GC机制与常用算法

缺点

需要扫描,标记两个流程,效率比较低(递归与全堆对象遍历)
清理出来的空闲内存是不连续的,会产生大量内存碎片
要暂停整个应用

复制算法

步骤:

1、将原有的内存空间分为两块,每次只使用其中一块
2、在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中
3、清除正在使用的内存块中的所有对象
4、交换两个内存的角色
Java GC机制与常用算法
优点:只需扫描一遍对象,高效

缺点:不适用于存活对象较多的场合,如新生代GC;浪费内存(50%);需暂停整个应用

标记-整理算法

从根节点开始,对所有可达对象做一次标记,将所有的存活对象压缩到内存的一端,清理边界外所有的空间
Java GC机制与常用算法

缺点:效率低

优点:适用于存活对象较多的场合,清除的内存连续

小结

效率

​ 复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)

内存整齐度

​ 复制算法=标记/整理算法>标记/清除算法

内存利用率

​ 标记/整理算法=标记/清除算法>复制算法

分代收集算法

应用于当前商业虚拟机的GC。根据对象的存活周期的不同将内存划分为几块儿。一般是把Java堆分为新生代和老年代:短命对象归为新生代,长命对象归为老年代

少量对象存活,适合复制算法大量对象存活,适合用标记-清理/标记-整理老年代的对象中,有一小部分是因为在新生代回收时,老年代做担保,进来的对象;绝大部分对象是因为很多次GC都没有被回收掉而进入老年代

问题

Java中,GC的应用范围

内存区域中的程序计数器、虚拟机栈、本地方法栈这3个区域随着线程而生,线程而灭,因此不需要考虑回收问题
Java堆和方法区:一个接口中的多个实现类需要的内存可能不同,一个方法中的多个分支需要的内存也可能不一样,只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,因此需要考虑回收问题