/ java  

JVM再探-垃圾收集器和内存分配策略

java程序内存的分配和释放都是由JVM进行管理的,新建一个对象等分配了一定的内存后,当对象不再被引用时,就需要释放对应的内存,这些都统一归JVM管理,对于JVM的内存释放我们称之为垃圾回收.

垃圾收集器算法

JVM垃圾收集算法简单分为3类,标记-清除算法,复制算法以及标记-整理算法.

  • 标记-清除算法
    标记内存中不再被引用的对象,然后在标记完成后进行统一回收.
    不足:一是效率问题,标记和清除的过程都需要花费一定时间;二是标记清除后产生大量不连续的内存碎片,当大的对象生成时需要分配空间就无法分配了.
    代表收集器: CMS收集器,G1收集器

  • 复制算法
    将内存区域一分为二(A和B),一开始内存分配时都用A部分进行分配,当进行回收时,将A上存活的内存直接复制到B上,然后清空A上所有内存的使用.
    不足:一半空间浪费掉了;当存活下来的对象比较多时需要进行较多的复制操作.
    在垃圾收集器的新生代中都基本采用该回收算法.我们一般将堆内存划分为新生代和老年代,新生代发生的垃圾回收称为young GC,老年代发生的垃圾回收称为full GC.
    年轻代分为Eden,S0和S1三个部分,一开始这3个区域被使用都是0,随着新生代内存的不断分配,Eden在逐渐增加,当达到100%时会发生一次young GC,此时存活下来的对象会被放在S0,然后Eden被清空,下一次young GC时,将S0和Eden中存活下来的对象移至S1,清空S0和Eden.
    代表收集器:Serial收集器

  • 标记-整理算法
    标记内存中不再被引用的对象,然后将存活下来的对象都移动到一端,接着直接清楚掉边界端以外的内存。
    不足:相对与标记-清楚算法多了一部移动存活对象的操作,可能在耗时上会多一些.
    代表收集器:Serial Old收集器,Parallel Old收集器

垃圾收集器

垃圾收集器实际上是对上面垃圾回收算法的一个具体实现,新生代和老年代可以使用的垃圾收集器如下图:
垃圾收集器图
从图中可以看到当使用某个新生代的垃圾收集器时,其对应的老年代的垃圾收集器可选的种类是确定的,一般实际开发中常用ParNew和CMS。

  • Serial收集器
    单线程版本的垃圾收集器,采用复制算法,进行的垃圾回收时会暂停所有的用户线程。

  • Serial Old收集器
    单线程版本的垃圾收集器,采用标记-整理,进行垃圾回收时会暂停所有的用户线程。

  • ParNew收集器
    实际上是Serial的多线程版本,在进行垃圾回收时会由多个线程进行回收,进行垃圾回收时还是会暂停所有的用户线程。

  • Parallel Scavenge收集器
    多线程进行垃圾回收,采用复制算法,与ParNew的区别时是可以通过配置相关参数调节暂停时间和回收的吞吐量(运行用户代码时间/(运行用户代码时间+垃圾收集时间)),进行垃圾回收时会暂停所有的用户线程

  • Parallel Old收集器
    多线程进行垃圾回收,采用标记-整理算法,和Parallel Scavenge类似也是注重吞吐量,进行垃圾回收时会暂停所有的用户线程。

  • CMS收集器
    采用标记-清楚算法,注重最短回收停顿时间,可以在进行垃圾回收时不暂停用户线程,分为初始标记(STW)->并发标记->重新标记(STW)->并发回收。由于采用标记-清楚算法,所以会产生内存碎片,最终导致无法给大的对象分配内存,所以一般当老年代内存占用没到达100%就促发full GC,这个促发百分比可以通过相关参数可以设置。

  • G1收集器
    整体看来采用标记-整理算法,局部Region之间采用复制算法,注重最短回收停顿时间,分为初始标记(STW)->并发标记->最终标记(STW)->筛选回收(STW)四个阶段。