JVM垃圾收集算法

垃圾收集算法和Sun JVM 垃圾收集器简介

垃圾收集基础

Java 语言的一大特点就是可以进行自动垃圾回收处理,而无需开发人员过于关注系统资源,例如内存资源的释放情况。自动垃圾收集虽然大大减轻了开发人员的工作量,但是也增加了软件系统的负担。拥有垃圾收集器可以说是 Java 语言与 C++语言的一项显著区别。在 C++语言中,程序员必须小心谨慎地处理每一项内存分配,且内存使用完后必须手工释放曾经占用的内存空间。当内存释放不够完全时,即存在分配但永不释放的内存块,就会引起内存泄漏,严重时甚至导致程序瘫痪。

垃圾对象的判定
  • 引用计数法

    引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,计数器-1,当计数器为0的时候,JVM就认为对象不再被使用,是“垃圾”了。引用计数法实现简单,效率高;但是不能解决循环引用问问题(A对象引用B对象,B对象又引用A对象,但是A,B对象已不被任何其他对象引用),同时每次计数器的增加和减少都带来了很多额外的开销,所以在JDK1.1之后,这个算法已经不再使用了。

  • 根搜索算法

    根搜索方法是通过一些称为“GC Roots”对象作为起点,从这些节点开始往下搜索,搜索通过的路径成为引用链(Reference Chain),当一个对象没有被GC Roots的引用链连接的时候,说明这个对象是没有被引用的,可以回收。
    GC Roots对象包括:

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

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

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

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

垃圾收集算法
  • 标记清除法(Mark-Sweep)

    标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段首先通过根搜素算法标记出被引用的对象,未被标记的对象就是可以被回收的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。该算法最大的问题是回收垃圾对象后产生大量的内存碎片,导致以后大对象的内存分配困难,可能提前触发又一次的垃圾回收动作。

  • 复制算法 (Copying)

    复制算法的基本思路是将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大。因此在真正需要垃圾回收的时刻,复制算法的效率是很高的。又由于对象在垃圾回收过程中统一被复制到新的内存空间中,因此,可确保回收后的内存空间是没有碎片的。该算法的缺点是始终有一部分内存空间未利用。Sun Hotspot JVM 的新生代串行垃圾回收器中使用了复制算法的思想。新生代分为 eden 空间、from 空间、to 空间 3 个部分。其中 from 空间和 to 空间可以视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。from 和 to 空间也称为 survivor 空间,即幸存者空间,用于存放未被回收的对象。在垃圾回收时,eden 空间中的存活对象会被复制到未使用的 survivor 空间中 (假设是 to),正在使用的 survivor 空间 (假设是 from) 中的年轻对象也会被复制到 to 空间中 (大对象,或者老年对象会直接进入老年带,如果 to 空间已满,则对象也会直接进入老年代)。此时,eden 空间和 from 空间中的剩余对象就是垃圾对象,可以直接清空,to 空间则存放此次回收后的存活对象。这种改进了的复制算法既保证了空间的连续性,又避免了大量的内存空间浪费。

  • 标记压缩算法(Mark-Compact)

    复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在年轻代经常发生,但是在老年代更常见的情况是大部分对象都是存活对象。如果依然使用复制算法,由于存活的对象较多,复制的成本也将很高。标记-压缩算法是一种老年代的回收算法,它在标记-清除算法的基础上做了一些优化。也首先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。

  • 增量算法 (Incremental Collecting)

    通常在垃圾回收过程中,应用程序线程会处于挂起状态(stop the world)暂停一切正常的工作,等待垃圾回收的完成。如果垃圾回收时间过长,应用程序会被挂起很久,将严重影响用户体验或者系统的稳定性。增量算法的基本思想是,如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

  • 分代收集算法 (Generational Collecting)

    分代收集算法是基于对象的生命周期不同和各种垃圾收集算法的优点综合起来的一个收集算法;它将内存空间划分为不同的几块,用来存放不同生命周期的对象,针对不同的内存区域采用对应的合适收集算法来收集垃圾对象。以 Sun Hot Spot 虚拟机为例,它将所有的新建对象都放入称为年轻代的内存区域,年轻代的特点是对象会很快回收,因此,在年轻代就选择效率较高的复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间。在老生代中,几乎所有的对象都是经过几次垃圾回收后依然得以幸存的。因此,可以认为这些对象在一段时期内,甚至在应用程序的整个生命周期中,将是常驻内存的。如果依然使用复制算法回收老生代,将需要复制大量对象。再加上老生代的回收性价比也要低于新生代,因此这种做法也是不可取的。根据分代的思想,可以对老年代的回收使用与新生代不同的标记-压缩算法,以提高垃圾回收效率。

Sun Jvm 垃圾收集器简介
  • 垃圾收集器搭配图
    gcimg

  • serial GC

    serial GC 是一个单线程的,采用复制收集算法的垃圾收集器,在工作的时候会导致应用程序停顿。

  • ParNew GC

    ParNew Gc 是一个多线程的,采用复制收集算法的垃圾收集器,在工作的时候会导致应用程序停顿。它和 Parallel Scavenge GC 不同的时它可以喝CMS收集器搭配工作。

  • Parallel Scavenge
Parallel Scavenge是一个多线程的,采用复制收集算法的垃圾收集器,在工作的时候会导致应用程序停顿。    
  • serial Old GC

    serial Old GC 是一个单线程的,采用标记清除整理算法的垃圾收集器,在工作的时候会导致应用程序停顿。

  • CMS GC

    CMS GC 是一个并发,低停顿的垃圾回收器,在工作的大部分时间中,应用线程可以和垃圾回收线程并发运行,不会一直导致应用程序处于停顿状态。

  • Parallel Old

    Parallel Old GC 是一个多线程,采用标记压缩算法的垃圾回收器,在工作的时候会导致应用程序停顿。

    GC 相关参数总结
  • 与串行回收器相关的参数

    -XX:+UseSerialGC:在新生代和老年代使用串行回收器。

    -XX:+SuivivorRatio:设置 eden 区大小和 survivor 区大小的比例。

    -XX:+PretenureSizeThreshold:设置大对象直接进入老年代的阈值。当对象的大小超过这个值时,将直 接在老年代分配。

    -XX:MaxTenuringThreshold:设置对象进入老年代的年龄的最大值。每一次 Minor GC 后,对象年龄 就加 1。任何大于这个年龄的对象,一定会进入老年代。

  • 与并行 GC 相关的参数

    -XX:+UseParNewGC: 在新生代使用并行收集器。

    -XX:+UseParallelOldGC: 老年代使用并行回收收集器。

    -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。

    -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时, 会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。

    -XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。

    -XX:+UseAdaptiveSizePolicy:打开自适应 GC 策略。在这种模式下,新生代的大小,eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间 的平衡点。

  • 与 CMS 回收器相关的参数

    -XX:+UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用 CMS+串行收集器。

    -XX:+ParallelCMSThreads: 设定 CMS 的线程数量。

    -XX:+CMSInitiatingOccupancyFraction:设置 CMS 收集器在老年代空间被使用多少后触发,默认为 68%。

    -XX:+UseFullGCsBeforeCompaction:设定进行多少次 CMS 垃圾回收后,进行一次内存压缩。

    -XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收。

    -XX:+CMSParallelRemarkEndable:启用并行重标记。

    -XX:CMSInitatingPermOccupancyFraction:当永久区占用率达到这一百分比后,启动 CMS 回收 (前提是-XX:+CMSClassUnloadingEnabled 激活了)。

    -XX:UseCMSInitatingOccupancyOnly:表示只在到达阈值的时候,才进行 CMS 回收。

    -XX:+CMSIncrementalMode:使用增量模式,比较适合单 CPU。

  • 与 G1 回收器相关的参数

    -XX:+UseG1GC:使用 G1 回收器。

    -XX:+UnlockExperimentalVMOptions:允许使用实验性参数。

    -XX:+MaxGCPauseMills:设置最大垃圾收集停顿时间。

    -XX:+GCPauseIntervalMills:设置停顿间隔时间。

  • 其他参数

    -XX:+DisableExplicitGC: 禁用显示 GC。

文章目录
  1. 1. 垃圾收集算法和Sun JVM 垃圾收集器简介
    1. 1.0.1. 垃圾收集基础
    2. 1.0.2. 垃圾对象的判定
    3. 1.0.3. 垃圾收集算法
    4. 1.0.4. Sun Jvm 垃圾收集器简介
    5. 1.0.5. GC 相关参数总结
|