您的位置:首页 > 编程语言 > Java开发

深入浅出java虚拟机系列:(二)GC&垃圾收集算法

2016-04-23 16:45 573 查看
1. 概述
     GC需要完成的三件事:
          哪些内存需要回收?
          什么时候回收?
          如何回收?
     其中程序计算器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭,不需要过多考虑回收的问题。
     而java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的。

2. 判断对象死亡or生存?(重要)
     heap里面存放着几乎所有的java对象,GC在对堆进行回收前,首先要判断哪些对象是存活着,哪些已经死去。
2.1 引用计数算法(Reference Counting)
     描述:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用时效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
     COM、Python语言使用这种算法,但是,主流java 虚拟机并没有选用这种算法来管理内存,最主要的原因是:它很难解决对象之间相互循环引用的问题。
2.2 可达性分析算法(Reachability Analysis)
     基本思路:通过一系列的称为“GC Roots“的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链相连(用图论,即从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
     主流商用语言,如java、C#等都是使用这种算法。
2.3 谈引用
     java将引用分为:强引用、软引用、弱引用、虚引用4种,这4种引用强度依次逐渐减弱。
     强引用:类似 Object obj = new Object();垃圾收集器永远不会回收引用所指对象;
     软引用:描述一些还有用但并非必需的对象;在系统内存吃紧,要发生OOM之前,会把这些对象列入第二次垃圾回收的范围中,如果回收之后内存还是不够,才会发生OOM。
     弱引用:也是用来描述有用但并非必需的对象;被弱引用关联的对象只能生存到下一次GG发生之前。当GC工作时,无论当前内存是否足够,都会回收被弱引用关联的对象;
     虚引用:无法通过虚引用取得一个对象的实例。设置虚引用的唯一目的是:能在这个对象被收集器回收时收到一个系统通知。
2.4 生存还是死亡
     要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析时发现没有与GC Roots 相连接的引用链,那它会被第一次标记并且进行一个筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法以及被虚拟机调用过,虚拟机将这两种情况都视为”没有必要执行“。
     如果被判定为有必要执行,则这个对象将会放置在F-Queue的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalize线程去执行它。finalize()是对象逃脱死亡命运的最后一次机会,稍后GC将对整个F-Queue中 的对象进行第二次小规模的标记。
2.5 回收方法区(也称永久代)
     在方法区中进行GC性价比一般比较低;
     永久代的垃圾回收主要包括两部分内容:废弃常量和无用的类;
     判断一个类是否是“无用的类”,需同时满足以下三个条件:
     a)该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例;
     b)加载该类的ClasLoader已经被回收;
     c)该类对应的java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法;

3. 垃圾收集算法(重要)
3.1 标记-清除算法(Mark-Sweep)
     缺点:a)效率问题:标记和清除两个过程的效率都不高;
               b)空间问题:产生大量碎片;
3.2 复制算法(Coping)(适用新生代)
     优点:实现简单,运行高效;
     缺点:内存浪费了一半;
     现在的商业虚拟机都采用这种算法来回收新生代;将新生代内存划分为一块较大的Eden和两块较小的Survivor空间(默认是8:1:1),当回收时将Eden和Survivor中还存活的对象一次性复制到另外的一块Survivor空间上,最后清理掉Eden和刚那块Survivor空间。
     HotSpot默认Eden和Survivor比例为8:1,只有10%的内存会被浪费。有时我们没办法保证每次回收都只有不多余10%的对象存活,当survivor不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。
3.3 标记-整理算法(Mark-Compact)(适用老年代)
     复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。
     标记-整理算法中标记仍然与标记-清除算法一样,但“整理”是让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
3.4 总结
     当前商业jvm的GC都采用”分代手机(Generational Collection)算法。
     新生代中:每次GC都有大批对象死去,只有少量存活,选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
     老年代:对象存活率高,没有额外空间对它进行分配担保,采用“标记-整理”算法。 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息