-
Notifications
You must be signed in to change notification settings - Fork 0
Description
原文链接:GC Tuning Confessions of a Performance Engineer by Monica Beckwith
很高兴再次为您提供最新的 Virtual JUG 大会概述。这次我们将讨论垃圾回收(GC)这个自动内存管理工具,它使我们和我们的应用相信,只要不一次性的消耗太多,就会有无限的内存。
本次会议由 Monica Beckwith 呈现。Monica是一个独立咨询顾问,她专门为企业应用 优化 Java 虚拟机和 垃圾回收器。她是各种会议的常客,还发表过 垃圾回收、Java内存模型等专题文章。Monica还曾引领 Oracle 的 G1GC 性能团队,被称为 JavaOne Rock Star。你可以在 Twitter 上关注 Monica @mon_beck。
下面是 Youtube 上的会议视频,点击查看:
以下附出一些相关资源(非原文内容)
- PPT MonicaBeckwith_ETE2015_GCTuningConfessions.pdf
- PPT MonicaBeckwith_ETE2015_GCTuningConfessions.pdf
- 在线查看PPT ETE 2015 – Monica Beckwith – GC Tuning Confessions of a Performance Engineer
- 在线查看PPT GC Tuning Confessions Of A Performance Engineer - Improved :)
垃圾回收(Garbage collection)
垃圾回收是任何现代化应用平台的重点之一。JVM也不例外,Java代码访问内存是由平台自动管理。
你不需要为你的对象明确分配多少字节空间,而且也不需要手动释放内存。虽然有许多实现内存管理的方法,但是 JVM 的垃圾回收无疑的最高水准的。
简而言之,所有分配在内存中的对象都是在堆中分配的。堆代表你的代码可以访问的所有内存。当堆上没有多余的空间的时候,释放内存的算法会在堆上移除对象,为你提供更多的空间。
并发和并行GC(Concurrent and parallel GC)
垃圾回收最重要地方在于 什么时候 如何运行。当然,抽象化的内存也会占用你的应用可用资源。
如果垃圾收集器和你的程序并行执行(部分GC被称为并发)。当GC使用很多线程并行的加速工作时,这时候被称为并行。目前在 JDK8 的实现中,默认的垃圾回收器是并行并发执行的,这使得垃圾回收真的很快。接下里让我们看一下 GC 会执行那些操作,讨论一下它们为什么那么重要,并且看一下它们是如何影响 GC 的性能的。
标记、移动 和 压缩(Marking, moving, and compacting)
垃圾回收器主要就是为了处理垃圾。这就意味着如果你的对象正在被应用使用的时候,垃圾回收之后,这个对象仍然会存在。不可达的对象会被认定为死亡,之后会被删除并释放内存。
这就需要一些标记来区分对象是死了还是活着。最简单的做法是标记对象图,从根对象开始,查看哪些对象仍然在被访问并且应该被保留。需要注意的是,你不需要要每次都遍历所有的堆去寻找存活的对象,因为这个操作代价太大,平台更希望把这些资源让给那些对实际业务有用的操作。所以它会通过两个阶段来进行操作:标记和清除。在标记阶段会找到存活的对象。然后把这些活动的对象移动到连续的内存空间区域中,从而使空闲的空间也是连续的内存区域,以便使用。可以把这个过程理解为磁盘碎片整理。
分代 GCs (Generational GCs)
虽然很容易实现一个只对单一内存块进行回收GC的算法,但是这并不是最优的。为了避免一次又一次的遍历所有的对象进行回收占用宝贵的资源,堆实际上是划分区域的,特定属性的对象会存放在特定的区域,这被称为分代(generations)。下图是 Oracle 垃圾回收基础教程(Oracle’s Garbage Collection Basics tutorial)阐述的分代图:
所有新创建的对象被放在新生代(Young generation)。实际上在大多数应用中,大部分的对象寿命并不长。如果对象在方法中创建,很可能在方法调用完毕之后就死了。
如果对象存活的时间足够长,那么他们将被移动到旧生代(Old generation),这里很少会执行GC。在新生代和旧生代发生的垃圾回收分别被称为 次要(Minor)和主要(major) 垃圾回收。
Minor 和 Major 垃圾回收
Minor垃圾回收 主要删除新生代的死对象。因为在这个回收发生的时候大多数对象是不可达的,所以速度会非常快。通常新生会还会有被划分出一个更小的区域,称为幸存者(Survivor)空间。在Minor垃圾回收之后仍然存活的对象会被移动到这个空间。如果经历和多次Minor垃圾回收仍然从幸存者空间幸存了下来,他们会继续变老,被移动到老年代(旧生代)。
Major 垃圾回收会在老年代运行。这个过程通过较慢,因为这里有更多的对象存活。毕竟他们已经存活了很长时间,才能升级到老年代。
虽然分代这种方式是有效的,但是这种方式与设置更大的堆是有冲突的。由于堆空间是按块划分的,堆越大块越大。Major GC 需要暂停虚拟机去分析老年代的整个内存,通常这个过程是很耗时的。而且对应用来说,这层抽象化的操作是不可见的。
欢迎 G1GC
G1GC 是一个区块化、并行、并发、增量的垃圾回收器,与HotSpot GC 相比,其暂停也是可预测的。分代垃圾回收的主要问题在于不够灵活。事实上,随着内存和硬件越来越便宜,应用程序也变的越来越饥渴。我们很自然的想要运行数百G内存的JVM。但是这么大的内存会让 GC 的暂停问题十分明显。想象一下,这么大的内存下有三分之一是老年代,可能会有 30G 的对象会被标记移动。JVM 的用户是不能容忍在一个较小的堆上有这么长时间的暂停的。这就是为什么我们不能在不牺牲性能或改变垃圾收集方式的情况下给予Java更多的内存。
给 Java 更多的内存就可能会带来性能消耗,这个性能消耗主要体现在垃圾回收上,内存越大,垃圾回收越慢,应用暂停的时间越长。
G1GC 对堆的划分不同于分代划分。事实上它将堆划分成数千个称之为区域(regions)的小空间。这样 GC 可以针对个别区域进行清理,并通过适当的措施,使每个 GC 事件尽可能的短。这就是 G1GC 的主要价值,在更大的堆上,实现可预测的 GC 暂停事件。
这个创举不是毫无副作用。G1GC 不得不需要记录 是否一个区域里的对象会引用另一个区域里的对象,类似于 GC 的根。并且这会和你的应用代码一块进行。这意味着着 GC 比以前更加占用 CPU,你的应用会得到更少的资源。
然而,基于区域粒度上增强了,这使得 GC 比以前更快了,而且 自适应性更强,可调整空间更大。
G1GC 选项列表
这里有一些 Monica 提到的命令用于调整 G1GC。
-XX:G1HeapRegionSize=n值是 2 的幂,范围是 1 MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域-XX:MaxGCPauseMillis=200GC可以暂停多长时间。 这只是个建议值,G1GC 暂停的时间会尽可能比这个值短。默认值是 200 毫秒-XX:G1HeapWastePercent=10设置您愿意浪费的堆百分比。 垃圾越大, GC 在新区域分配对象时越快,而不是试图将这些空间浪费掉
你还可以在 Oracle 博客上找到更多的选项和GC的基本准则。
采访演讲者
... 略
