垃圾回收与内存分配策略

垃圾回收

  • GC 并不是 Java 语言的伴生产物。诞生于1960年的Lisp语言(MIT)才是第一门真正使用内存动态分配 和 GC 技术的语言。
  • 为何要去了解 GC 和 内存分配, 换言之, 什么场景下需要对 GC 的参数进行调整呐?
    • 使用成型技术框架 (比如 Spark, 特别耗内存)时内存溢出, 内存泄漏(spark sql 使用不当可能会导致)等问题。
    • 当系统的性能受到 频繁 GC(Minor GC, Full GC, Full GC影响大很多) 影响时, 我们就需要对 GC 实施必要的监控 和 调节。

判断对象是否存活

  • 引用计数算法(Reference Counting)

    • 给对象添加一个引用计数器, 每当有一个地方引用它时, 计数器就加1。

    • 当引用失效时, 计数器值就减 1。

    • 任何计数器为0的对象是不可能再被使用的。

    • 应用领域:

      • 微软的COM技术(Component Object Model) 技术
      • ActionScript 3 的 FlashPlayer(已凉)
      • python 语言
      • Squirrel (游戏脚本领域)
    • Java 虚拟机中没有选用 引用计数法来管理内存的原因:

      • 很难解决对象之间相互循环引用的问题。

      • 代码

        package com.ronnie.gc;
        
        public class ReferenceCounting {
        
            public Object instance = null;
        
            private static final int _1MB = 1024 * 1024;
        
            // 占点内存, 方便查阅GC日志
            private byte[] bigSize = new byte[2 * _1MB];
        
            public static void testGC(){
                ReferenceCounting objA = new ReferenceCounting();
                ReferenceCounting objB = new ReferenceCounting();
                objA.instance = objB;
                objB.instance = objA;
        
                objA = null;
                objB = null;
        
                // 假设发生 GC, objA 和 objB 是否能被回收
                System.gc();
            }
        
            public static void main(String[] args) {
                testGC();
            }
        }
        
      • 执行后打印的GC日志:

        [GC (System.gc()) [PSYoungGen: 7999K->872K(75776K)] 7999K->880K(249344K), 0.0013158 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
        [Full GC (System.gc()) [PSYoungGen: 872K->0K(75776K)] [ParOldGen: 8K->655K(173568K)] 880K->655K(249344K), [Metaspace: 3440K->3440K(1056768K)], 0.0054783 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
        Heap
         PSYoungGen      total 75776K, used 650K [0x000000076b780000, 0x0000000770c00000, 0x00000007c0000000)
          eden space 65024K, 1% used [0x000000076b780000,0x000000076b822a68,0x000000076f700000)
          from space 10752K, 0% used [0x000000076f700000,0x000000076f700000,0x0000000770180000)
          to   space 10752K, 0% used [0x0000000770180000,0x0000000770180000,0x0000000770c00000)
         ParOldGen       total 173568K, used 655K [0x00000006c2600000, 0x00000006ccf80000, 0x000000076b780000)
          object space 173568K, 0% used [0x00000006c2600000,0x00000006c26a3d38,0x00000006ccf80000)
         Metaspace       used 3447K, capacity 4496K, committed 4864K, reserved 1056768K
          class space    used 376K, capacity 388K, committed 512K, reserved 1048576K
        
        Process finished with exit code 0
        
        
        • PSYoungGen: 7999K->872K(75776K) 说明产生了 GC, 也就是说虚拟机并没有因为这两个对象互相引用就不回收它们 => 虚拟机并不是通过引用计数算法来判断这两个对象是否存活的。
  • 可达性分析算法

    • 主流的商用编程语言 (Java, C#, 以及古老的Lisp) 的对象是否存活判断的算法实现都是 可达性分析(Reachability Analysis)。

    • 基本思想

      • 通过一系列的 GC Root 对象作为起始点, 从这些节点开始向下搜索, 搜索走过的路径称为引用链(Reference Chain)

      • 当一个对象 到 GC Roots 没有任何引用链相连(GC Roots 到这个对象不可达), 则证明此对象是不可用的。

      • 拓展的: 新生代GC搜索用深度优先算法, 老年代GC搜索用广度优先算法(忘了的去补图论)。

        img

    • Java 中可以作为 GC Roots 的对象:

      • 虚拟机栈 (栈帧中的本地变量表) 中引用的对象。
      • 方法区中类静态属性引用的对象。
      • 方法区中长常量引用的对象。
      • 本地方法栈中 JNI (即一般说的 Native 方法) 引用的对象。
  • 引用

    • 无论是通过引用计数法来判断对象的引用数量, 还是通过 可达性分析算法判断对象的引用链是否可达, 判断对象是否存活都与 “引用” 有关。

    • JDK 1.2 之前 Java中引用的定义:

      • 如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址, 就称这块内存代表着一个引用。
      • 定义纯粹, 但是太过狭隘, 一个对象在这种定义下就只有被引用 或者 没有被引用两种状态。
      • 我们希望描述这样一类特殊的对象:
        • 当内存空间足够时, 则能保留在内存中, 如果内存空间在进行GC后还是非常紧张, 则可以抛弃这些对象。
        • 很多系统的缓存功能都符合这样的应用场景。
    • JDK 1.2之后, Java对引用的概念进行了扩充, 将引用分为强引用(Strong reference), 软引用(Soft Reference), 弱引用(Weak Reference), 虚引用(Phantom Reference) 4 种, 强度依次减弱。

      • 强引用(Strong reference)

        • 指在程序代码之中普遍存在的, 类似 “Object obj = new Object()” 这类的引用, 只要强调引用还存在, 垃圾收集器永远不会回收掉被引用的对象。
      • 软引用(Soft Reference)

        • 描述一些还有用但并非必须的对象。

        • 对于软引用关联着的对象, 在系统将要发生内存溢出异常之前, 将会把这些对象列进回收范围之中进行第二次回收。

        • 如果这次回收还没有足够的内存, 才会抛出内存溢出异常。

        • 在 JDK 1.2 之后, 提供了SoftReference 类来实现软引用。

          package java.lang.ref;
          
          
          /**
           * Soft reference objects, which are cleared at the discretion of the garbage
           * collector in response to memory demand.  
           * 软引用对象, 会在垃圾回收时 根据 内存需求进行清理。
           * Soft references are most often used to implement memory-sensitive caches.
           * 软引用最多被应用的场景是 处理内存敏感的缓存
           *
           * <p> Suppose that the garbage collector determines at a certain point in 
           * time that an object is <a href="package-summary.html#reachability">softly
           * reachable</a>.  
           * 假设垃圾回收器认定某个时间一个时间的特定点的一个对象是软可获取的。
           *
           * At that time it may choose to clear atomically all soft
           * references to that object and all soft references to any other
           * softly-reachable objects from which that object is reachable through a 
           * chain of strong references.  
           * 在那个时间, 它可能会选择去原子性地清除所有指向该对象的软引用, 并且所有指向其他软可
           * 获取的对象的 软引用都可以通过强引用连接获取。
           * 
           * At the same time or at some later time it will enqueue those newly-cleared 
           * soft references that are registered with reference queues.
           * 同时或者 稍后, 它会将这些已经注册到引用队列中的 新清理的软引用 放入队列
           *
           * <p> All soft references to softly-reachable objects are guaranteed to have
           * been cleared before the virtual machine throws an
           * <code>OutOfMemoryError</code>. 
           * 在虚拟机抛出 OOM 异常之前, 所有指向软可获取的对象的软引用 都 一定会被清理完。
           *
           * Otherwise no constraints are placed upon the time at which a soft reference 
           * will be cleared or the order in which a set of such references to different 
           * objects will be cleared.  
           * 否则 当一个软链接被清除时 或者 引用指向不同对象的引用集合被清除的顺序 清除时, 就不
           * 会有任何限制。
           *
           * Virtual machine implementations are, however, encouraged to bias against 
           * clearing recently-created or recently-used soft references.
           * 然而虚拟机 的应用 更偏向于 不清理 最近创建的 和 最近被使用的软引用
           *
           * <p> Direct instances of this class may be used to implement simple caches;
           * this class or derived subclasses may also be used in larger data structures
           * to implement more sophisticated caches.
           * 该类的直接实例可能被用于实现简单的缓存, 此类 或者继承此类的子类 可能会被用于大的数
           * 据结构来实现更成熟的缓存。
           *
           * As long as the referent of a soft reference is strongly reachable, that is, 
           * is actually in use, the soft reference will not be cleared.  
           * 只要一个软引用指向的对象是强可获取的, 并且在被使用中, 那么软引用就不会被清除。
           *
           * Thus a sophisticated cache can, for example, prevent its most recently used 
           * entries from being discarded by keeping strong referents to those entries, 
           * leaving the remaining entries to be discarded at the discretion of the 
           * garbage collector.
           * 因此, 一个成熟的缓存可以(举例)通过对 最近最多使用的 引用entries 维持强引用 来 防
           * 止它被 丢弃。剩下的 引用 entries 会在 垃圾收集器 分离时被丢弃。
           *
           * @author   Mark Reinhold
           * @since    1.2
           */
          
          public class SoftReference<T> extends Reference<T> {
          
              /**
               * Timestamp clock, updated by the garbage collector
               * 由垃圾回收器 更新的 时间戳时钟
               */
              static private long clock;
          
              /**
               * Timestamp updated by each invocation of the get method.  
               * 每次调用 get 方法时 都会更新 时间戳
               * The VM may use this field when selecting soft references to be cleared, 
               * but it is not required to do so.
               * 当虚拟机可能使用该域时可能会清除软引用, 但这不是必须的的。
               */
              private long timestamp;
          
              /**
               * Creates a new soft reference that refers to the given object. 
               * 创建指向该对象的新的软引用
               * The new reference is not registered with any queue.
               * 该引用没有被注册到任何队列中
               * @param referent object the new soft reference will refer to
               */
              public SoftReference(T referent) {
                  super(referent);
                  this.timestamp = clock;
              }
          
              /**
               * Creates a new soft reference that refers to the given object and is
               * registered with the given queue.
               * 创建一个指向该对象的新的软引用, 且该引用被注册到了已得的队列中
               *
               * @param referent object the new soft reference will refer to
               * @param q the queue with which the reference is to be registered,
               *          or <tt>null</tt> if registration is not required
               *
               */
              // ReferenceQueue 是一个通过 volatile 关键字修饰实现的引用队列
              public SoftReference(T referent, ReferenceQueue<? super T> q) {
                  super(referent, q);
                  this.timestamp = clock;
              }
          
              /**
               * Returns this reference object's referent. 
               * 返回该引用的对象
               * If this reference object has been cleared, either by the program or by 
               * the garbage collector, then this method returns <code>null</code>.
               * 如果该引用对象已经被清除了(无论是程序或者垃圾回收器执行的), 该方法都会返回
               * null
               * @return   The object to which this reference refers, or
               *           <code>null</code> if this reference object has been cleared
               */
              public T get() {
                  T o = super.get();
                  if (o != null && this.timestamp != clock)
                      this.timestamp = clock;
                  return o;
              }
          
          }
          
          
      • 弱引用(Weak Reference)

        • 也是用于描述非必须对象的, 但是它的强度比软引用更弱一些, 被弱引用关联的对象只能生存到下一次GC发生之前。

        • 当垃圾收集器工作时, 无论当前内存是否足够, 都会回收掉只被弱引用关联的对象。

        • JDK1.2 之后, 提供了 WeakReference 来实现软引用。

          package java.lang.ref;
          
          
          /**
           * Weak reference objects, which do not prevent their referents from being
           * made finalizable, finalized, and then reclaimed.  
           * 软引用对象, 并不能保护他们指向的对象 不被 finalizable, finalized 再重申
           *
           * Weak references are most often used to implement canonicalizing mappings.
           * 软引用被应用于实现最简洁的 mappings
           *
           * <p> Suppose that the garbage collector determines at a certain point in 
           * time that an object is <a href="package-summary.html#reachability">weakly
           * reachable</a>.
           * 假设 垃圾回收器 确定了在一个特定的时间点 的一个对象是 弱可获取的。 
           * 
           * At that time it will atomically clear all weak references to
           * that object and all weak references to any other weakly-reachable objects
           * from which that object is reachable through a chain of strong and soft
           * references.
           * 这时, 它会原子性地清理所有指向该对象的弱引用 并且 所有指向其他可以通过强引用链和软
           * 引用链获取的弱可获取的对象的 弱引用。
           *
           * At the same time it will declare all of the formerly weakly-reachable 
           * objects to be finalizable. 
           * 同时, 它会声明所有的正式的 弱可获取对象 为 finalizable。
           * 
           * At the same time or at some later time it will enqueue those newly-cleared 
           * weak references that are registered with reference queues.
           * 在同一时间或者 稍后, 它会将这些已经注册在 引用队列中的新被清理的软引用 放入队列中
           *
           * @author   Mark Reinhold
           * @since    1.2
           */
          
          public class WeakReference<T> extends Reference<T> {
          
              /**
               * Creates a new weak reference that refers to the given object. 
               * 创建指向该对象的新的软引用
               * The new reference is not registered with any queue.
               * 该引用没有被注册到任何队列中
               * @param referent object the new weak reference will refer to
               */
              public WeakReference(T referent) {
                  super(referent);
              }
          
              /**
               * Creates a new weak reference that refers to the given object and is
               * registered with the given queue.
               * 创建一个指向该对象的新的软引用, 且该引用被注册到了已得的队列中
               *
               * @param referent object the new weak reference will refer to
               * @param q the queue with which the reference is to be registered,
               *          or <tt>null</tt> if registration is not required
               */
              public WeakReference(T referent, ReferenceQueue<? super T> q) {
                  super(referent, q);
              }
          
          }
          
      • 虚引用(Phantom Reference)

        • 是最弱的一种引用关系。

        • 一个对象是否有虚引用的存在, 完全不会对其生存时间构成影响。

        • 也无法通过虚引用来取得一个对象实例。

        • 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时找到一个系统通知。

        • JDK1.2 之后提供了 PhantomReference 类来实现虚引用。

          package java.lang.ref;
          
          
          /**
           * Phantom reference objects, which are enqueued after the collector
           * determines that their referents may otherwise be reclaimed.  
           * 虚引用对象, 在收集器确认 软引用对象指向的对象 可能不会被重申后将它们放入队列。
           *
           * Phantom references are most often used for scheduling pre-mortem cleanup 
           * actions in a more flexible way than is possible with the Java finalization 
           * mechanism.
           * 虚引用 最多被应用于 将检尸 行为 安排得更灵活, 而不 仅是 使用 Java finalization 
           * 机制。
           *
           * <p> If the garbage collector determines at a certain point in time that the
           * referent of a phantom reference is <a href="package-
           * summary.html#reachability">phantom reachable</a>, then at that
           * time or at some later time it will enqueue the reference.
           * 假设 垃圾回收器 确定了在一个特定的时间点 的一个对象是 虚可获取的, 那么 在这个时间
           * 或者稍后, 它就会进入引用队列。
           *
           * <p> In order to ensure that a reclaimable object remains so, the referent 
           * of a phantom reference may not be retrieved: 
           * 为了确保一个可重申的对象维持该状态, 一个虚引用执行的对象可能不会被检索
           * The <code>get</code> method of a phantom reference always returns 
           * <code>null</code>.
           * 虚引用的 get 方法 总是返回 null
           *
           * <p> Unlike soft and weak references, phantom references are not
           * automatically cleared by the garbage collector as they are enqueued.  
           * 不像软引用和弱引用, 虚引用 并不是 由 垃圾收集器 在 它们进入队列时自动清理的。
           *
           * An object that is reachable via phantom references will remain so until all
           * such references are cleared or themselves become unreachable.
           * 一个 可以通过软引用获取的 对象会维持原样 直到 所有 引用都被清除, 或者 指向的对象自
           * 己变为了不可获取状态。
           *
           * @author   Mark Reinhold
           * @since    1.2
           */
          
          public class PhantomReference<T> extends Reference<T> {
          
              /**
               * Returns this reference object's referent.  
               * 返回该引用指向的对象
               * Because the referent of a phantom reference is always inaccessible, 
               * this method always returns <code>null</code>.
               * 因为仅虚引用指向的对象往往是不可获取的, 该方法总会返回 null
               *
               * @return  <code>null</code>
               */
              public T get() {
                  return null;
              }
          
              /**
               * Creates a new phantom reference that refers to the given object and
               * is registered with the given queue.
               * 创建一个指向该对象的新的软引用, 且该引用被注册到了已得的队列中。
               *
               * <p> It is possible to create a phantom reference with a <tt>null</tt>
               * queue, but such a reference is completely useless: Its <tt>get</tt>
               * method will always return null and, since it does not have a queue, it
               * will never be enqueued.
               * 虽然创建虚引用时也可能会附带一个空队列, 但这毫无使用价值, 由于 get 方法一直是
               * 返回 null, 既然它没有队列, 那么它就不会被放入队列中。 
               *
               * @param referent the object the new phantom reference will refer to
               * @param q the queue with which the reference is to be registered,
               *          or <tt>null</tt> if registration is not required
               */
              public PhantomReference(T referent, ReferenceQueue<? super T> q) {
                  super(referent, q);
              }
          
          }
          

垃圾收集算法

  • 标记清除法(Mark-Sweep 算法)

    • 是基础的收集算法, 后续的收集算法都是基于这种思路并对其不足进行改进而得到的。

    • 标记阶段

      • 首先标记出所有需要回收的对象, 在标记完成后统一回收所有被标记的对象。
    • 清除阶段

      • 状态值为0的则被清除。
    • 不足之处

      • 效率问题: 标记和清除两个过程的效率都不高。
      • 空间问题: 标记清除后会产生大量不连续的内存碎片, 而空间碎片过多可能会导致以后在程序运行过程中需要分配大对象时, 无法找到足够的连续内存而不得不提前触发另一次GC。
    • 示意图

      img

  • 复制法(Copying)

    • 大致思想

      • 将可用内存容量划分为大小相等的两块, 每次只使用其中一块。
      • 当这一块内存用完了, 就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清除掉。
    • 优点

      • 每次都是对整个半区进行内存回收, 内存分配时就不用了考虑内存碎片等复杂情况, 只要移动堆顶指针, 按顺序分配内存即可(实现简单, 运行高效)。
    • 缺点

      • 将内存缩小为原来的一半, 代价过高。

      • 在对象存活率较高时就要进行较多的复制操作, 效率将会变低。

      • 如果不想要浪费 50% 的空间, 就需要更多的空间进行分配担保, 以应对被使用的内存中所有对象都 100% 存活的极端情况。因此, 老年代一般不能直接选用这种算法。

        img

  • 标记整理法(Mark-Compact)

    • 标记过程

      • 与 “标记-清除” 算法一样
    • 整理过程

      • 不是对可回收对象进行清理, 而是让存活的对象都向一端移动, 然后直接清理掉端边界以外的内存
    • 示意图

      img

  • 分代收集法(Generational Collection)

    • 当代商业虚拟机都采用的垃圾回收算法
    • 概述
      • 根据对象存活周期的不同将内存划分为几块。
      • 一般把 Java 堆分为新生代 和 老年代。
      • 根据各个年代的特点采用最适当的收集算法。
        • 在新生代中, 每次GC都发现有大批对象死去, 只有少量存活, 就选用复制算法。
        • 老年代中, 由于对象存活率高, 没有额外空间对它进行分配担保, 就必须使用 标记整理法 或 标记清理法来进行回收。

垃圾收集器

  • 是内存回收的具体实现

    image-20191107104251399

    HotSpot虚拟机的垃圾回收机制

  • Serial 收集器

    • 最基本, 发展最悠久的收集器, 在 JDK1.3.1 之前是虚拟机新生代收集的唯一选择。
    • 是运行在 Client 模式下的虚拟机的默认垃圾收集器。
    • 是一个单线程的收集器, 在进行垃圾收集时, 必须暂停其他所有的工作线程。
    • 缺点:
      • "Stop The World" 是由虚拟机在后台自动发起和自动完成的, 在用户不可见的情况下把用户正常工作的线程全部停掉, 这对很多应用来说都是难以接受的。
  • ParNew 收集器

    • Serial 收集器的多线程版本

    • 是许多运行在Server模式下的虚拟机中首选的新生代收集器

    • 除Serial收集器外, 只有它能与CMS收集器配合工作(jdk 1.7-1.8版本)

    • 包含Serial收集器可用的所有控制参数:

      • -XX:SurvivorRatio
      • -XX:PreteenureSizeThreshold
      • -XX:HandlePromotionFailure
    • 收集算法、Stop The World、对象分配规则、回收策略都与Serial收集器完全一样。

    • 在 CPU 核数非常多的环境下, 可以使用 --XX: ParallelGCThreads 参数来限制垃圾收集的线程数。

  • Parallel Scavenge 收集器

    • 新生代收集器, 也是使用复制算法的多线程收集器。

    • 特点:

      • CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间
      • 而 Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)
      • 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
      • 高吞吐量可以有效地利用 CPU 时间, 尽快完成程序的运算任务, 主要适合在后台运算而不需要太多交互的任务
    • 提供了两个参数用于精准控制吞吐量

      • -XX:MaxGCPauseMillis: 最大垃圾收集停顿时间
      • -XX:GCTimeRatio: GC时间比例(直接控制吞吐量)[默认99]
    • GC 自适应调节策略(GC Ergonomics)

      • -XX:+UseAdaptiveSizePolicy

      • 开启后就不需要手工指定新生代的大小(-Xmn), Eden 与 Survivor 区的比例 (-XX:SurvivorRatio), 晋升老年代对象大小(-XX:PretenureSizeThreshold) 等细节参数了。

      • 虚拟机会根据当前系统的运行情况收集性能监控信息, 动态调整这些参数以提供最合适的停顿时间或者最大吞吐量。

  • Serial Old 收集器

    • Serial 收集器的老年代版本, 同样是一个单线程收集器。

    • 使用 标记整理算法

    • 主要被Client模式下的虚拟机使用。

    • 在Server模式下的用途:

      • 在 JDK1.5 以及它之前的版本中与 Parallel Scavenge 收集器搭配使用。
      • 作为 CMS 收集器的后备预案, 在并发收集器发生 Concurrent Mode Failure 时使用。
  • Parallel Old 收集器

    • Parallel Scavenge 收集器 的老年代版本

    • 使用多线程和标记整理算法

    • JDK1.6之后才有该收集器, 解决了此前新生代 Parallel Scavenge 收集器一直处于比较尴尬的状态:

      • 如果新生代选择了 Parallel Scavenge 收集器, 老年代除了 Serial Old (PS MarkSweep) 收集器外别无选择。
      • 而由于 老年代 Serial Old 收集器在服务端应用性能上的‘拖累’, 使用了 Parallel Scavenge 收集器也未必能在整体应用上获得吞吐量最大化的效果。(有时还不如 ParNew + CMS)
    • Parallel Scavenge + Parallel Old 应用场合:

      • 注重吞吐量和CPU资源敏感的场合。
  • CMS(Concurrent Mark Sweep) 收集器

    • 是以获取最短回收停顿时间为目标的收集器。

    • 应用

      • 互联网站 服务端
      • B / S 系统 服务端
      • 等重视服务的响应速度, 希望系统停顿时间最短, 以给用户带来较好的的体验的应用
    • 基于 “标记-清除” 算法实现, 运作过程

      • 初始标记 (CMS initial mark)
      • 并发标记 (CMS concurrent mark)
      • 重新标记 (CMS remark)
      • 并发清除 (CMS concurrent sweep)
    • 优点

      • 并发收集
      • 低停顿
    • 缺点

      • 对 CPU 资源非常敏感

      • 无法处理浮动垃圾(Floating Garbage)

        • 可能出现 “Concurrent Mode Failure” 失败而导致另一次Full GC 的产生。

        • 浮动垃圾:

          • 由于 CMS 并发清理阶段线程还在运行着, 伴随程序运行会有新的垃圾不断产生, 这一部分垃圾出现在标记过程之后, CMS 无法在当次收集中处理掉它们, 只好留待下一次GC时再清理掉的垃圾。
        • CMS 激活阈值:

          • 参数: -XX:CMSInitiatingOccupancyFraction

          • JDK1.6: 92%

        • 如果 CMS 运行期间预留的内存无法满足需要, 就会出现一次 “Concurrent Mode Failure” 失败, 这时虚拟机会启动后备预案:

          • 临时启用 Serial Old 收集器 来重新进行老年代的垃圾收集, 停顿时间会很长。
      • 收集结束时会有大量碎片产生, 提前触发 Full GC

        • 调整参数:

          • -XX:+UseCMSCompactAtFullCollection(默认开启) , 用于在 CMS 收集器顶不住要进行 FullGC时开启内存碎片的合并整理过程, 代价是停顿时间变长。

          • -XX:CMSFullGCsBeforeCompaction, 用于设置执行多少次不压缩的 Full GC 后, 跟着来一次带压缩的 Full GC

  • G1(Garbage First) 收集器

    • 当今收集技术发展的最前沿成果之一, 是一款面向服务应用的垃圾收集器。
    • 是 HotSpot 为了 替换掉 CMS 收集器而开发的
    • 与其他 GC 收集器相比的特点:
      • 并行与并发
        • 能充分利用多CPU, 多核环境下的硬件优势, 使用多个 CPU(或CPU 核心) 来缩短Stop-The-World 停顿的时间。部分其他收集器原本需要停顿Java线程执行的GC操作, G1收集器仍可以通过并发的方式让java程序继续执行。
      • 分代收集
        • 保留分代概念
        • 可以不需要其他收集器配合就能独立管理整个 GC 堆
        • 能采用不同的方式去处理创新的对象和已经存活了一段时间, 熬过多次GC的旧对象以获取更好的收集结果
      • 空间整合
        • 从整体看, G1是基于 标记整理算法实现的。
        • 从局部(两个 Region)上来看, 是基于复制算法实现的。
        • G1 运作期间不会产生内存空间碎片, 收集后能提供规整的内存。
        • 有利于程序长时间运行, 分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
      • 可预测的停顿
        • 是 G1 相对于 CMS 的另一大优势
        • 降低停顿时间是 G1 和 CMS 共同的关注点, 但 G1 除了追求低停顿外, 还能建立可预测的停顿时间模型, 能让使用者 明指定一个长度为 M 毫秒的时间片段内, 消耗在垃圾收集上的时间不得超过 N 毫秒, 这几乎已经是实时 Java(RTSJ) 的垃圾收集器的特征。
      • 进行GC的范围
        • G1 将整个 Java 堆划分为多个大小相等的独立区域(Region), 虽然还保留有新生代和老年代的概念, 但是 新生代 和 老年代 不再是物理隔离的了, 它们都是一部分 Region(不需要连续) 的集合。
      • 为何 G1 能建立 可预测的停顿时间模型?
        • 因为它可以有计划的避免在整个Java堆中进行全区域的垃圾收集。
        • G1 追踪各个 Region 里面垃圾堆积的价值大小(回收所获得的空间大小及回收所需的时间的经验值), 在后台维护一个优先列表, 每次根据允许的收集时间, 优先回收价值最大的 Region。
        • 从而在有限的时间内可以获取尽可能高的收集效率。
      • G1 中对象引用
        • Region 之间的对象引用 以及 其他收集器中的新生代老年代对象引用, 虚拟机是使用 Remembered Set 来避免全堆扫描的。
        • G1中每个 Region 都有一个对应的 Remembered Set
        • 虚拟机发现程序 在 对 Reference 类型的数据进行 写操作时, 会产生一个Write Barrier 暂时中断 写操作, 检查 Reference 引用对象是否处于不同的 Region 之中。
        • 如果是, 便通过 CardTable 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 之中。
        • 当进行内存回收时, 在GC根节点 的 枚举范围 加入到 Remembered Set 即可保证不对全堆扫描也不会有有遗漏。
      • 如果不计算维护 Remembered Set 的操作, G1收集器的运作大致可划分为以下几个步骤:
        • Initial Marking (初始标记)
        • Concurrent Marking (并发标记)
        • Final Marking (最终标记)
        • Live Data Counting and Evacuation (筛选回收)
  • image-20191106194951015

垃圾收集器参数总结

参数 描述
UseSerialGC 虚拟机运行在 Client 模式下的默认值, 打开次开关后, 使用Serial + Serial Old 的收集器组合进行内存回收
UseParNewGC 使用 ParNew + Serial Old 的收集器组合进行内存回收
UseConcMarkSweepGC 使用 ParNew + CMS + Serial Old 的收集器组合进行内存回收, Serial Old 收集器作为 CMS 收集器出现 Concurrent Mode Failure 失败后的后备收集器使用。
UseParallelGC 虚拟机运行在Server模式下的默认值, 使用 Parallel Scavenge + Serial Old (PS MarkSweep) 的收集器组合进行内存回收。
UseParallelOldGC 使用 Parallel Scavenge + Parallel Old 的收集器进行内存回收
SurvivorRatio 新生代中 Eden 区域 与 Survivor 区域的容量 比值, 默认为8, 代表 Eden:Survivor = 8 : 1
PretenureSizeThreshold 直接晋升到老年代的对象大小, 设置这个参数后, 大于这个参数的对象将直接在老年代分配
MaxTenuringThreshold 晋升到老年代的对象年龄。每个对象在坚持过一次 Minor GC 之后, 年龄就增加1, 当超过这个参数值时就进入老年代
UseAdaptiveSizePolicy 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄
ParallelGCThreads 设置并行 GC 时进行内存回收的线程数
GCTimeRatio GC 时间占用总时间的比率, 默认值为99, 即允许 1% 的 GC 时间, 仅在使用 Parallel Scavenge 收集器时生效。
MaxGCPauseMillis 设置 GC 的最大停顿时间。 仅在使用 Parallel Scavenge 收集器时生成。
CMSInitiatingOccupancyFraction 设置 CMS 收集器 在老年代空间被使用多少后出发垃圾回收, 默认值为 68%
UseCMSCompactAtFullCollection 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理。仅在使用 CMS 收集器时生效。
CMSFullGCsBeforeCompaction 设置 CMS 收集器 在进行若干次垃圾回收后再启动一次内存碎片整理, 仅在使用 CMS 收集器时生效。
原文地址:https://www.cnblogs.com/ronnieyuan/p/11815756.html