码迷,mamicode.com
首页 > 编程语言 > 详细

垃圾回收算法和JVM垃圾收集器(一)

时间:2015-05-13 00:24:34      阅读:266      评论:0      收藏:0      [点我收藏+]

标签:

参考文献:深入理解Java虚拟机 周志明

                Java编程思想 Bruce Eckel

 

为什么自动化垃圾回收后还要了解GC呢:当需要排查各种溢出、内存泄漏问题时,当垃圾收集成为系统达成更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

 

垃圾回收的主要区域:Java堆和方法区

 

那么,如何判断对象是否已经可以回收呢?主要有两种方法:

一、引用计数算法:每个对象都有一个引用计数器,当有引用连接至对象时,引用计数器加1;当引用计数器离开引用作用域被置null时,引用计数减1。垃圾回收器会在含有全部对象的列表上遍历,找出那么引用计数为0的对象,就释放其占有的空间。COM、FlashPlayer、Python都是用引用计数算法实现内存管理的。但是,这种方法有个缺陷,就是不能解决交互自引用的对象组的问题(也就是循环引用)。

二、可达性分析算法:基本依据——通过一系列的称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象不可用。

在Java语言中,可以作为GCRoots的对象包括以下几种:

     a) 虚拟机栈(栈帧中的本地变量表)中引用的对象。

     b) 方法区中类静态属性引用的对象。

     c) 方法区中常量引用的对象。

     d) 本地方法栈中JNI(Native方法)引用的对象。

 

  那么,引用的定义又是怎么样的呢?JDK1.2后,将引用分为四类:强引用(普遍存在的对象),软引用(有用但非必需,二次回收),弱引用(只要垃圾收集器开始工作,必定被回收,不管堆内存是不是够用),虚引用(往往用于对象被收集器回收时能收到一个系统通知).

  在可达性搜索算法中不可达的对象,也并非是“非死不可的”,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finallize()方法或该对象的finalize()已经被调用过,虚拟机将这两种情况都视为“没有必要执行”。
  如果一个对象被判断为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条虚拟机自动建立的,低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象在fianlize()方法中执行缓慢,或者发生了死循环,将很可能会导致F-Queue队列中的其它对象永久处于等待状态,甚至导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象在finalize()中成功拯救自己--只要重新与引用链上的任何一个对象建立关联既可,那在第二次标记时它将被移出“即将回收”集合;如果对象这时候还没有逃脱,那它就将被回收了。

 

  上面说的都是关于堆中对象垃圾回收的判定算法,接下来将是回收方法区的内容。方法区的垃圾收集主要回收两部分内容:废弃常量和无用的类。回收废弃常量与回收Java堆中的对象非常类似。假设有某个字符串常量,没有任何String对象引用字符串常量池的这个常量,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个常量就会清理出常量池。常量池中的其他类、方法、字段的符号引用也与此类似。

判断一个类为无用的类,需要满足3个条件:

  a) 该类所有的实力都已经被回收。

      b) 加载该类的ClassLoader已经被回收。

      c) 该类对应的java.class.Lang对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

 

下面是垃圾收集算法的核心:

1.标记-清除算法
  该算法是最基础的搜集算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所有说它是最基础的算法是因为后续的收集算法都是基于这种思路并对其缺点进行改进得到的。它的缺点主要有两个:一个是效率问题,标记和清除过程效率都不高;另外一个是空间问题,标记清除后会产生大量不连续内存碎片,内存碎片太多导致当程序运行进需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集操作。标记-清除算法的执行过程如下图:

1.标记-清除算法
  该算法是最基础的算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。之所有说它是最基础的算法是因为后续的收集算法都是基于这种思路并对其缺点进行改进得到的。它的缺点主要有两个:一个是效率问题,标记和清除过程效率都不高;另外一个是空间问题,标记清除后会产生大量不连线内存碎片,内存碎片太多导致当程序运行进需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集操作。

2.复制算法
  为了解决效率问题,“复制”算法出现了。它将可用内存按容量分为大小相等的两块,每次只使用其中一块。当这一块内存使用完了,就将还存活着的对象复制到另外一块上,然后再把已使用过的内存空间一次性清理空。这样使得每次都是对其中一块进行内存回收,内存分配时也不用考虑内存碎片的问题,只需要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,代价太高了一点。现代虚拟机将内存分为一块较大的Eden空间、from空间、to空间3个部分,其中from空间和to空间可视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。from空间和to空间也可称为survivor空间,用于存放未被回收的对象。在垃圾回收时,eden空间中的存活对象的会被复制到未使用的survivor空间中,正在使用的survivor空间中的年轻对象也会被复制到to空间中(大对象,或者老年对象会直接进入老年代,如果to空间已满,则对象也会直接进入老年代)。HotSpot虚拟机默认Eden和Survivor空间比例为8:1。这个比例的依据是新生代的大部分对象都是“朝生夕死”的。

3.标记-整理算法
  复制算法在对象存活率较高时就要执行较多的复制操作,效率将会变低,如果不想浪费50%的空间,就需要有额外的空间进行担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。根据老年代的特点,“标记-整理”算法被提出,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清除,而是让所有存活对象都向一端移动,然后直接清除掉端边界以外的内存。

 

 

  

垃圾回收算法和JVM垃圾收集器(一)

标签:

原文地址:http://www.cnblogs.com/navitmin-home/p/4498896.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!