jvm垃圾回收

来源:互联网 发布:疾病数据库 编辑:程序博客网 时间:2024/04/29 06:55
在这节里不打算大量讨论算法实现,只是简单的介绍一下基本思想以及发展过程。最基础的搜集算法是标记-清除算法Mark-Sweep),如它的名字一样,算法分层标记清除两个阶段,首先标记出所有需要回收的对象,然后回收所有需要回收的对象,整个过程其实前一节讲对象标记判定的时候已经基本介绍完了。说它是最基础的收集算法原因是后续的收集算法都是基于这种思路并优化其缺点得到的。它的主要缺点有两个,一是效率问题,标记和清理两个过程效率都不高,二是空间问题,标记清理之后会产生大量不连续的内存碎片,空间碎片太多可能会导致后续使用中无法找到足够的连续内存而提前触发另一次的垃圾搜集动作。

  为了解决效率问题,一种称为复制Copying)的搜集算法出现,它将可用内存划分为两块,每次只使用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次过清理掉。这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,未免太高了一点。 

  现在的商业虚拟机中都是用了这一种收集算法来回收新生代,IBM有专门研究表明新生代中的对象98%是朝生夕死的,所以并不需要按照11的比例来划分内存空间,而是将内存分为一块较大的eden空间和2块较少的survivor空间,每次使用eden和其中一块survivor,当回收时将edensurvivor还存活的对象一次过拷贝到另外一块survivor空间上,然后清理掉eden和用过的survivorSun Hotspot虚拟机默认edensurvivor的大小比例是8:1,也就是每次只有10%的内存是浪费的。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有10%以内的对象存活,当survivor空间不够用时,需要依赖其他内存(譬如老年代)进行分配担保(Handle Promotion)。 

  复制收集算法在对象存活率高的时候,效率有所下降。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保用于应付半区内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法。因此人们提出另外一种标记-整理Mark-Compact)算法,标记过程仍然一样,但后续步骤不是进行直接清理,而是令所有存活的对象一端移动,然后直接清理掉这端边界以外的内存。 

  当前商业虚拟机的垃圾收集都是采用分代收集GenerationalCollecting)算法,这种算法并没有什么新的思想出现,只是根据对象不同的存活周期将内存划分为几块。一般是把Java堆分作新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法,譬如新生代每次GC都有大批对象死去,只有少量存活,那就选用复制算法只需要付出少量存活对象的复制成本就可以完成收集。


二、垃圾收集算法

1、标记-清除算法(Mark-Sweep)

原理:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点:容易产生内存碎片。

2、复制算法(copying)

原理:先把内存分为大小相等的二个部分,每次只使用其中的一块,当这一块内存回收时,就把还存活的对象规则的复制到另一块内存中,以此循环。
  缺点:需要复制,效率降低、浪费空间。
  现代的虚拟机实现基本都采用这种算法来手机新生代。

3、标记-整理算法(Mark-Compact)

原理:类似于标记-清除,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

4、分代收集算法(Generational Collection)

原理:根据对象存活周期的不同将内存划分为几块。一般是java堆分为新生代和老年代,这样就可以根据各个年代的特点采用合适的收集算法。

三、回收方法区

  方法区的垃圾回收主要回收两部分:废弃常量和无用的类。


  回收废弃常量和java堆中的对象非常类似。当没有对象引用它时即可回收。


  回收无用的类则比较苛刻:
        该类所有的实例都已经被回收,即java堆中不存在该类的任何实例。
        加载该类的ClassLoader已经被回收。
        该类对应的java.lang.Class对象没有在任何地方被引用


0 0
原创粉丝点击