GC

作者: Wi1ls努力努力再努力 | 来源:发表于2019-05-03 15:09 被阅读0次

参考:

  • 《深入理解 JVM&G1 GC》- 周明耀[略过 3、5、7 章]
  • 《垃圾回收的算法与实现》

以 JVM 来说,JVM 的的实现是由 c/c艹实现的,众所周知Java 是不需要手动管理内存的,因为内存的分配和回收都是依靠底层的 c/c艹,对于 Java 是完全透明的。
GC 主要是算法上的事,分为可达性分析,内存的管理区域划分(内存分代),内存的回收机制。
所有的GC 在进行 GC 时,都会进入一个 Stop The Word(STW) 的时刻。因为在 GC 的时候,如果内存还在变换的话,会影响 GC 回收。

Stop The Word:为了防止 GC 线程在进行垃圾回收的时候 mutator还在不断得生成垃圾,因此在进行 GC 的时候需要将 mutator 的线程挂起,这个时间也称为暂停时间。mutator 的线程在需要挂起的时候不会马上就挂起,会执行到该线程的 safepoint 才会进行挂起。等mutator 的所有线程都执行到 safepoint 并且挂起时,gc 线程就会开始工作。

下面整篇讨论的都是 JVM 的 GC。

  • 虚拟机会单独运行一个线程专门用来 GC。
  • HotSpot 虚拟机中,Java 的实例在内存中的布局分为对象头、实例数据、对齐填充。对象头包含实例在运行时的标记。如_mark(如哈希、GC 分代、锁标志),_metadata(指向类元数据)。
  • 内联:方法调用由方法体直接替换,避免开辟新的函数栈。
  • 垃圾标记算法:
    • 计数算法:
      • 无法解决循环引用
    • 根搜索算法:
      • 根对象集合包括(Java 栈内对象引用、本地方法栈对象引用、运行时常量池对象引用、方法区类静态属性对象引用、类对象的 Class 对象)
      • 当一个对象要被回收的时候,会调用其 finalize()方法并且终身只调一次。如果在 finalize()中没有自救,则会被回收。jvm 会对热点代码进行本地编译,而jvm 启动的时候可以设置热点代码的阈值,当方法调用超过这个阈值,则会启动编译。可以猜测内部对于方法有一个方法执行的计数器。因此才可以确保 finalize()只调用一次。
  • 比较常见的有标记-清除(Mark-Sweep)、复制(Copying)、标记-压缩(Mark-Compact)
    • 标记-清除。

      • collector 从 mutator 根对象开始遍历,对可访问对象的 header 标记为可达。随后对 heap 进行线性遍历,回收不可达对象。
      • 效率低下,容易产生内存碎片。
    • 复制。

      • 内存分为两块,每次保持一块空闲。会将另外一块存活对象复制到空闲块。清除正在使用的内存块对象。交换内存块角色。
      • 内存使用率折半。
      • 因为 JVM 绝大对象都是瞬时状态,生命周期非常短暂。Copying 广泛适用于年轻代。
    • 标记-压缩

      • 标记出垃圾对象后,将存活对象移动到规整联系的空间,执行 Full GC。此时已用和未用内存各自一边。分配对象时使用指针碰撞(Bump the Pointer)进行内存分配。
    • 增量算法(Incremental Collecting)

      • 为了减少 STW,GC 线程一次只收集一小片区域,与应用程序线程切换。反复进行。但是增加了线程切换和上下文转换消耗,造成吞吐量下降。
    • 分代收集算法(Generational Collection)

      • 将内存区间根据对象特点划分几块,根据每一块内存区的特点,选取不同的回收算法提供垃圾回收效率。
      • 一般讲对象分为年轻代、老年代、持久代。不同的生命周期对象使用不同的算法。
    • GC

      • GC 的工作任务分为内存的动态分配和垃圾回收。
      • 现在几乎所有的 GC 都采用分代手机算法。Java 堆分为年轻代(YoungGen)和老年代(OldGen),年轻代又可以分为 Eden、From Survivor、To Survivor。
      • 内存空间如何划分完全依赖 GC 设计。
      • 评估 GC 的性能:吞吐量、垃圾收集开销、 暂停时间、收集频率、堆空间、快速
    • JVM 的 GC

      • Serial/Serial Old 收集器、ParNew 收集器、Parallel/Parallel Old 收集器、CMS(Concurrent-Mask-Sweep)收集器、G1(Garbage-First)收集器。
      • 分类
        • 按线程:串行 Collector 和并行 Collector。
        • 按工作模式:并发 Collector 和 独占式 Collector
        • 处理方式:压缩式 Collector 和非压缩式 Collector
        • 按工作内存区间:年轻代 Collector 和老年代 Collector
    • Serial GC(串行收集器)

      • 作用于年轻代,采用复制算法、串行回收和 STW机制。默认作为 HotSpot Client 模式下的年轻代 Collector
      • 还提供 Serial Old Collector 供老年代使用。使用标记-压缩、串行回收和 STW 机制
  • ParNew GC
    • ParNew GC 是 Serial GC 的多线程版。
    • 采用并行回收,其余与 Serial GC 一致。
    • 单线程下,Serial GC 效率比 ParNew GC 效率高,因为后者需要线程切换
  • Parallel GC
    • 采用复制算法、并行回收和 STW 机制。
    • 可以控制吞吐量,被称为吞吐量优先的垃圾收集器。并且可以控制 GC 执行频率和 STW 的暂停时间阈值。PS:在 GC 中,吞吐量和低延迟是矛盾的。
    • Parallel Old GC 采用标记-压缩算法、并行回收、STW机制。
    • 吞吐量优先的使用场景,可以使用 Parallel GC 和 Parallel Old GC 组合。
  • CMS GC(Concurrent-Marking-Sweep)
    • 基于并行回收,老年代 GC。低延迟。采用标记-清除、STW 机制。
    • CMS 执行阶段:初始标记(Initial-Mark)、并发标记(Concurrent-Mark)、再次标记(Remark)、并发清除(Concurrent-Sweep)
      • STW -> initial-Mark - 恢复应用线程 ->并发标记(GC 线程与应用线程同时运行)(Concurrent-Mark) -> STW -> Remark -> Concurrent-Sweep
    • Serial Old GC 和 Parallel Old GC 使用标记-压缩避免 Full GC 产生碎片,然后使用指针碰撞(Bump the Pointer)分内新对象内存。CMS 使用标记-清除,因此只能选择空闲列表(Free List)执行内存分配。
    • CMS 可以设置 Full GC 后进行压缩整理,会增加停顿时间。也可以设置 N 次 Full GC 后进行压缩整理。
    • CMS与应用线程并发执行,相互抢占 CPU,因此会影响吞吐量。
    • 因为 CMS 在工作的时候,mutator 还在产生垃圾,而这些垃圾只能在下次 GC 清除。因此可能会造成本次 GC 失败,失败后将会启动 Serial Old GC 进行。
  • Garbage First(G1) GC
    • G1 的核心在于将内存分为若干大小相等的 Region,每个 Region
      不指定分代,在运行时动态指定。并且内存回收管理以 Region 为单位。譬如某一 Region 前一刻是存储年轻代,当被回收后,后一刻可能就被用来存储老年代了。
    • 进行 GC 时,G1 计算每个 Old 内存活对象数量并且在 sweep 阶段进行评分。开始混合回收时,年轻代尽量回收,老年代根据评分选择性回收。
    • 每一个 Region 都有个一个 RemberedSet(RSet),维护了其他 Region 对该 Region 中对象的引用。
    • 有一个 Collection Set(CSet),包含了GC 要回收的 Region 的集合。
    • GC 的过程分为:初始标记阶段、根区间扫描阶段、并行标记阶段、重标记阶段、清除阶段

相关文章

网友评论

      本文标题:GC

      本文链接:https://www.haomeiwen.com/subject/bmmvnqtx.html