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方法)引用的对象
根搜索算法的扩展:标记-清除算法、复制算法、标记-整理算法
标记-清除算法
两个阶段:标记阶段和清除阶段
标记阶段:通过根节点,标记所有从根节点开始的可达对象
清除阶段:清除所有未被标记的对象
缺点
需要扫描,标记两个流程,效率比较低(递归与全堆对象遍历)
清理出来的空闲内存是不连续的,会产生大量内存碎片
要暂停整个应用
复制算法
步骤:
1、将原有的内存空间分为两块,每次只使用其中一块
2、在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中
3、清除正在使用的内存块中的所有对象
4、交换两个内存的角色
优点:只需扫描一遍对象,高效
缺点:不适用于存活对象较多的场合,如新生代GC;浪费内存(50%);需暂停整个应用
标记-整理算法
从根节点开始,对所有可达对象做一次标记,将所有的存活对象压缩到内存的一端,清理边界外所有的空间
缺点:效率低
优点:适用于存活对象较多的场合,清除的内存连续
小结
效率
复制算法>标记/整理算法>标记/清除算法(此处的效率只是简单的对比时间复杂度,实际情况不一定如此)
内存整齐度
复制算法=标记/整理算法>标记/清除算法
内存利用率
标记/整理算法=标记/清除算法>复制算法
分代收集算法
应用于当前商业虚拟机的GC。根据对象的存活周期的不同将内存划分为几块儿。一般是把Java堆分为新生代和老年代:短命对象归为新生代,长命对象归为老年代。
少量对象存活,适合复制算法 ; 大量对象存活,适合用标记-清理/标记-整理,老年代的对象中,有一小部分是因为在新生代回收时,老年代做担保,进来的对象;绝大部分对象是因为很多次GC都没有被回收掉而进入老年代
问题
Java中,GC的应用范围
内存区域中的程序计数器、虚拟机栈、本地方法栈这3个区域随着线程而生,线程而灭,因此不需要考虑回收问题
Java堆和方法区:一个接口中的多个实现类需要的内存可能不同,一个方法中的多个分支需要的内存也可能不一样,只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,因此需要考虑回收问题