bingoogolapple.github.io icon indicating copy to clipboard operation
bingoogolapple.github.io copied to clipboard

JVM 垃圾收集算法

Open bingoogolapple opened this issue 7 years ago • 6 comments

垃圾收集简介

手动内存管理

  • 存储共享数据,必须显式地进行内存分配和内存释放。如果忘记释放, 则对应的那块内存不能再次使用。内存一直被占着,却不再使用,这种情况就称为内存泄漏

智能指针

  • 第一代自动垃圾收集算法, 使用的是引用计数。针对每个对象, 只需要记住被引用的次数, 当引用计数变为0时, 这个对象就可以被安全地回收了
  • 循环引用,导致引用计数一直大于零,还是会出现内存泄漏的情况

标记-清除

  • 垃圾收集根元素(Garbage Collection Roots)包括:局部变量(Local variables)、活动线程(Active threads)、静态域(Static fields)、JNI引用(JNI references)
  • Marking(标记):遍历所有的可达对象,并在本地内存(native)中分门别类记下
  • Sweeping(清除):这一步保证了不可达对象所占用的内存在之后进行内存分配时可以重用

bingoogolapple avatar Jul 24 '17 09:07 bingoogolapple

GC 算法基础

标记可达对象

  • 有一些特定的对象被指定为 Garbage Collection Roots(GC 根元素),包括:当前正在执行的方法里的局部变量和输入参数、活动线程(Active threads)、内存中所有类的静态字段(static field)、JNI 引用
  • 在标记阶段需要暂停所有应用线程

删除不可达对象

标记-清除算法(Mark and Sweep)
  • 直接忽略所有的垃圾。也就是说在标记阶段完成后,,所有不可达对象占用的内存空间都被认为是空闲的,因此可以用来分配新对象
  • 缺点:内存碎片
标记-清除-整理算法(Mark Sweep Compact)
  • 将所有被标记的对象(存活对象)迁移到内存空间的起始处,消除了标记-清除算法内存碎片的缺点
  • 缺点:GC 暂停时间会增加, 因为需要将所有对象复制到另一个地方,然后修改指向这些对象的引用
标记-复制算法(Mark and Copy)
  • 和标记-清除-整理算法(Mark Sweep Compact)十分相似: 两者都会移动所有存活的对象。区别在于, 标记-复制算法是将内存移动到另外一个空间(存活区)
  • 优点:标记和复制可以同时进行
  • 缺点:需要一个额外的内存区间来存放所有的存活对象

bingoogolapple avatar Jul 24 '17 09:07 bingoogolapple

Serial GC(串行 GC)

  • 要启用此款收集器,只需要指定一个 JVM 启动参数「-XX:+UseSerialGC」即可,同时对年轻代和老年代生效
  • Serial GC 对年轻代使用 mark-copy(标记-复制)算法, 对老年代使用 mark-sweep-compact(标记-清除-整理)算法。两者都是单线程的垃圾收集器,不能进行并行处理。两者都会触发全线暂停(STW),停止所有的应用线程
  • 这种 GC 算法不能充分利用多核 CPU。不管有多少 CPU 内核, JVM 在垃圾收集时都只能使用单个核心
  • 该选项只适合几百 MB 堆内存的 JVM,而且是单核 CPU 时比较有用。 对于服务器端来说,因为一般是多个 CPU 内核, 并不推荐使用, 除非确实需要限制 JVM 所使用的资源。大多数服务器端应用部署在多核平台上, 选择 Serial GC 就表示人为的限制系统资源的使用。 导致的就是资源闲置, 多的 CPU 资源也不能用来降低延迟,也不能用来增加吞吐量

bingoogolapple avatar Jul 24 '17 09:07 bingoogolapple

Parallel GC(并行 GC)

  • 要在年轻代启用此款收集器,只需要指定一个 JVM 启动参数「-XX:+UseParallelGC」即可
  • 要在老年代启用此款收集器,只需要指定一个 JVM 启动参数「-XX:+UseParallelOldGC」即可
  • 通过命令行参数「-XX:ParallelGCThreads=n」来指定 GC 线程数。其默认值为 CPU 内核数
  • 对年轻代使用 mark-copy(标记-复制)算法, 对老年代使用 mark-sweep-compact(标记-清除-整理)算法。两者都使用多线程,并行执行。两者都会触发全线暂停(STW),停止所有的应用线程
  • 并行垃圾收集器适用于多核服务器,主要目标是增加吞吐量。因为对系统资源的有效使用,能达到更高的吞吐量
    • 在 GC 期间,所有 CPU 内核都在并行清理垃圾,所以暂停时间更短
    • 在两次 GC 周期的间隔期,没有 GC 线程在运行,不会消耗任何系统资源
  • 因为此 GC 的所有阶段都不能中断,所以并行 GC 很容易出现长时间的停顿。如果减少延迟时间是系统的主要目标,那么就应该选择其他垃圾收集器组合
    • 长时间卡顿的意思是,此 GC 启动之后,属于一次性完成所有操作,于是单次 pause 的时间会较长

bingoogolapple avatar Jul 24 '17 09:07 bingoogolapple

Concurrent Mark and Sweep(并发标记-清除)

  • 要启用此款收集器,只需要指定一个 JVM 启动参数「-XX:+UseConcMarkSweepGC」即可
  • 对年轻代采用并行 STW 方式的 mark-copy(标记-复制)算法算法,对老年代主要使用并发 mark-sweep(标记-清除)算法。「缺点:老年代内存碎片问题」
  • CMS 的设计目标是避免在老年代垃圾收集时出现长时间的卡顿。主要通过两种手段来达成此目标
    • 不对老年代进行整理,而是使用空闲列表(free-lists)来管理内存空间的回收
    • 在 mark-and-sweep(标记-清除)阶段的大部分工作和应用线程一起并发执行(在这些阶段并没有明显的应用线程暂停。但值得注意的是,它仍然和应用线程争抢 CPU 时间。默认情况下, CMS 使用的并发线程数等于 CPU 内核数的 1/4)
  • 如果服务器是多核 CPU,并且主要调优目标是降低延迟,那么使用 CMS 是个很明智的选择。减少每一次 GC 停顿的时间,会直接影响到终端用户对系统的体验,用户会认为系统非常灵敏。因为多数时候都有部分 CPU 资源被 GC 消耗,所以在 CPU 资源受限的情况下,CMS 会比并行 GC 的吞吐量差一些

bingoogolapple avatar Jul 24 '17 09:07 bingoogolapple

G1 – Garbage First(垃圾优先算法)

  • 要启用此款收集器,只需要指定一个 JVM 启动参数「-XX:+UseG1GC」即可
  • G1 最主要的设计目标是将 STW 停顿的时间和分布变成可预期以及可配置的。是一款软实时垃圾收集器, 也就是说可以为其设置某项特定的性能指标。如:任意 1 秒暂停时间不得超过 5 毫秒,G1 会尽力达成这个目标
  • 堆不再分成连续的年轻代和老年代空间。而是划分为多个(通常是 2048 个)可以存放对象的小堆区(smaller heap regions),最小为 1Mb,最大为 32Mb。每个小堆区都可能是 Eden 区,Survivor 区或者 Old 区。在逻辑上,所有的 Eden 区和 Survivor 区合起来就是年轻代,所有的 Old 区拼在一起那就是老年代
  • 不必每次都去收集整个堆空间,而是以增量的方式来处理:每次只处理一部分小堆区,称为此次的回收集(collection set)。每次暂停都会收集所有年轻代的小堆区,但可能只包含一部分老年代小堆区
  • 在并发阶段估算每个小堆区存活对象的总数。用来构建回收集(collection set)的原则是:垃圾最多的小堆区会被优先收集。这也是 G1 名称的由来:garbage-first
  • G1的开销会更大。所以, 如果系统属于吞吐量优先型的, 又或者CPU持续占用100%, 而又不在乎单次GC的暂停时间, 那么CMS是更好的选择
  • 优点:解决了 CMS 中的各种疑难问题,包括暂停时间的可预测性,并终结了堆内存的碎片化。对单业务延迟非常敏感的系统来说,如果 CPU 资源不受限制,那么 G1 可以说是 HotSpot 中最好的选择「G1 适合大内存,需要低延迟的场景」
  • 缺点:G1 的开销会更大。所以,如果系统属于吞吐量优先型的,又或者 CPU 持续占用 100%,而又不在乎单次 GC 的暂停时间, 那么 CMS 是更好的选择

G1 年轻代收集

  • 堆一整块内存空间,被分为多个 heap 区
  • 年轻代内存由一组不连续的 heap 区组成,这使得在需要时很容易进行容量调整
  • 年轻代的垃圾收集会有 STW 事件
  • 年轻代 GC 通过多线程并行进行
  • 存活的对象被拷贝到新的 Survivor 区或者老年代

G1 老年代收集

  • 初始标记(Initial Mark)
    • 此时会有一次 STW 事件。在 G1 中,这附加在一次正常的年轻代 GC。标记可能有引用指向老年代对象的 Survivor 区(根区)
  • 扫描根区域(Root Region Scanning)
    • 扫描 Survivor 区中引用到老年代的引用。这个阶段应用程序的线程会继续运行。在年轻代 GC 可能发生之前此阶段必须完成
  • 并发标记(Concurrent Marking)
    • 在整个堆中查找活着的对象。此阶段应用程序的线程正在运行。此阶段可以被年轻代 GC 打断
  • 再次标记(Remark)
    • STW,完成堆内存中存活对象的标记。使用一个叫做 snapshot-at-the-beginning(SATB,起始快照)的算法,该算法比 CMS 所使用的算法要快速的多
  • 清理(Cleanup)
    • STW 在存活对象和完全空闲的区域上执行统计擦写 Remembered Sets。重置空 heap 区并将他们返还给空闲列表
  • 拷贝(Copying)
    • 产生 STW 事件来转移或拷贝存活的对象到新的未使用的 heap 区。只在年轻代发生时日志会记录为 [GC pause (young)]。如果在年轻代和老年代一起执行则会被日志记录为 [GC Pause (mixed)]

bingoogolapple avatar Jul 26 '17 03:07 bingoogolapple

你好,请问在初始标记(Initial Mark)中:

此时会有一次 STW 事件。在 G1 中,这附加在一次正常的年轻代 GC。标记可能有引用指向老年代对象的 Survivor 区(根区)

这里的Survivor区是指Young Generation中的Survivor Space吗?

luckyqiao avatar Jun 26 '18 07:06 luckyqiao