GC调优入门笔记

想给项目代码作作调优但有许多疑惑,好比有哪些参数要调、怎么调、使用什么工具、调优的效果如何定量测量等。发现Oracle的这份资料不错,简洁直接,回答了个人许多问题,给了许多很实用的大方向上的指导。将其中精华记录下来,但愿能给一样入门的朋友一些启示。html

Garbage Collectors

垃圾收集器 (Garbage Collectors)是JVM中的内存管理工具。它的职责包括:java

  • 在年轻代为对象分配空间,并将存活比较久的对象移动到年老代;
  • 在堆占用率超过某阈值时触发concurrent marking phase,在年老代找到存活的对象;
  • 触发parallel copying,压缩活着的对象,释放垃圾空间。

看起来有点抽象,而且貌似没提到年轻代的垃圾收集,其实已经在第一条中提到了:“将存活比较久的对象移动到年老代”,这里隐含了对年轻代进行存活对象登记和收集的过程。简而言之,垃圾收集器的职责是:给对象分配内存;收集年轻代垃圾;收集年老代垃圾程序员

串/并行Garbage Collector的选择

通常来讲,JVM会根据系统的物理配置等因素选择一个默认的垃圾处理器。但显然,不一样的应用程序有不一样的行为(如使用内存的频率、对象的平均存活时间);也有不一样的要求(若有界面的程序要求响应快速,而服务器端程序要求吞吐量高,能处理更多的请求)。因此,根据不一样程序的特色,可能须要不一样的垃圾处理器来管理。在此处,咱们先从串/并行的角度浅浅地了解一下这个问题。web

垃圾处理器能够粗略地分为串行进行和并行进行的,即垃圾处理这个过程在单线程仍是多线程中进行;在Java SE 1.4以前的版本不支持并行。根据Amdahl's law (程序可以经过并行来加速的程度取决于程序中必须串行运行的部分),若是GC是串行进行的,则一个并行的应用程序的加速程度会受到GC的影响。假设咱们经过增长处理器个数的方式来加速一个应用程序,那么随着处理器个数的增多,GC拖后腿的程度也愈来愈厉害,看下图:服务器

GC时间所占的百分比随处理器个数的变化多线程

这是一个数学模拟图,模拟了一个理想(彻底并行)的应用程序的吞吐量受GC时间的影响。横轴表明处理器个数,纵轴表明吞吐量,不一样颜色的曲线表明GC百分比不一样的程序。红色曲线表示一条在单CPU下GC时间占1%的程序,在处理器个数增长到32个时,GC占整个程序运行时间的百分比竟超过了20%。oracle

能够看到GC所占的时间百分比越大,拖后腿的程度就越厉害。这是一个很好理解的现象,由于GC是串行的,因此其运行时间不受处理器数量的影响。随着处理器的增多,应用程序自己的运行时间降低了,因此显得GC所占的时间百分比愈来愈大。分布式

所以,在小系统上开发应用时能够忽略的一些GC小问题,当扩展到大型系统上时就会变得十分可观,甚至成为性能瓶颈。可是,此时在垃圾处理器上作一些小文章就有可能极大地增长性能。好比考虑到上图反映的现象,或许咱们能够考虑换一个并行的垃圾处理器以提升吞吐量。ide

另外一方面,小型应用若是不须要其余特殊的GC行为,一般使用串行垃圾处理器就够了,选择其余垃圾处理器可能反而会引入额外的复杂性和开销。工具

分代模型

在处理垃圾时,须要先找到全部活着的对象,而后将剩下的做为垃圾进行处理。“找到全部活着的对象”这个过程须要耗费的时间与活着的对象数量成正比,这样的话,若是应用中原本就维护了大量的存活对象,那么找到活着的对象须要耗费大量的时间。为了优化这个过程,JVM程序员们基于一些经验提出了分代收集的思想。在这些经验中,最重要的是分代假设,即大部分对象都只存活很短的时间。

对象寿命的典型分布图

上图中,横轴表明总的字节分配数,即时间轴;纵轴表明不一样时间下存活的对象所占字节数。左边的尖峰表明分配空间没多久就能够回收的对象,好比在某个loop中临时分配的对象,它们的寿命只有一个loop的时间;最右边表明存活好久的那些对象,好比初始化时就存在且一直活到程序结束的对象;在这两极之间,有一些用于中间计算的对象,即左边的尖峰右边的这个包。

不一样应用程序的对象寿命分布图是不同的,可是许多应用的大体分布都符合上面这个图,这为分代收集奠基了一个很好的事实基础:大部分对象都在年轻时死去。

好比在一个公园里扫落叶,腾出空地让行人行走。有一些树掉叶子特别厉害,一小会儿地上就掉满了;而另一些树每小时只掉一两片叶子。假设清洁工为了省力,每过一段时间就清扫一次,扫完了则回到椅子上休息。若是我是清洁工,确定会选择集中打扫那些掉得厉害的树,并且可能会以比较高的频率打扫;至于那些掉得不厉害的树,只要偶尔看一下,等落满的时候再打扫就行了。若是每次都要把全部的树下打扫一遍,为了照顾那些掉得厉害的树个人打扫频率须要很高,我会很累,并且打扫时间也会变长,效率下降。

除了串行收集器和G1以外,其余收集器默认使用如下分代模型:

默认分代模型

模型分为年轻代和年老代,年轻代分为eden和两个survivor,virtual空间表明JVM向操做系统预订但还未实际分配的空间。

调优指标

maximum pause time

pause time是指垃圾处理器中止应用程序的运行,专一于空间释放时所花的时间。若是使用的是并行垃圾处理器,能够通过-XX:MaxGCPauseMillis=<nnn>这个命令行参数设按期望的最大pause time。(若是未设置,默认没有最大时间要求)

垃圾处理器会维护每次垃圾收集pause time的均值和方差,当均值与方差的和大于设置的MaxGCPauseMillis参数时,垃圾处理器会认为停留时间目标未达到,而后调整堆的大小和其它的有关参数来试图达到目标。

此处的maximum pause time和下面即将提到的throughput是一对相爱相杀的姐妹。一般减少堆size会优化pause time(扫描、处理时间减小),可是堆变小形成GC频率升高,从而致使throughput降低。对于这二者,垃圾处理器的处理方式是优先达到设置的pause time目标,其次再达到throughput目标。

throughput

吞吐量经过GC时间比例测量。 GC时间比例 = GC时间 / (GC时间 + 应用运行时间),其中的GC时间包括全部代的GC时间。若是使用的是并行垃圾处理器,吞吐量能够经过-XX:GCTimeRatio=<nnn>设置,若<nnn>为19,则GC时间比例为1/(1+19)=5%,即垃圾处理的时间占总时间的5%。

若是GCTimeRatio未达到要求,垃圾收集器会增长年轻代和年老代的大小来下降GCTimeRatio

footprint

内存占用(memory footprint)指程序运行时占用和引用的内存大小。

若是前面两个目标达到了,垃圾处理器会自动收缩堆,直至其中一个目标再也不知足(必定是throughput,由于堆变小会使停留时间变短),而后再试图知足这个目标。

promptness

及时性 (promptness)定义为对象死去以后到对象所占用的内存可使用以前的时间。这个指标对分布式系统一般比较重要。

通常调优策略

  1. 若是堆已经达到maximum heap size但throughput目标还未达到,说明设置的maximum heap size过小,能够尝试将其设为接近物理内存但还不至于致使内存交换的值。若是仍是达不到throughput目标,说明这个throughput目标对于当前平台上的内存大小来讲太高了。
  2. 若是throughput目标已经知足,但停留时间过长,则能够增长maximum pause time目标。但这样throughput目标有可能又得不到知足了,此时须要根据本身的判断做一个折中。
  3. 如上文所说,throughput与pause time对堆大小的要求相反,是一对相互竞争的指标。它们之间的相互竞争可能形成的结果是:即便应用程序已经在稳定运行了,堆大小仍然在上下振动。这代表垃圾处理器努力在二者之间寻找一个平衡。

度量

以上所说的throughput等指标须要根据应用的不一样特性去测量。好比要测一个web server的throughput,能够用一个本身写的client load generator;测试Solaris系统上服务器的内存占用,能够用pmap这个命令;若要测GC的停留时间,则能够经过命令行参数-verbose:gc直接观察JVM的诊断输出。

总结

本文定性介绍了GC调优的一些初级概念,为实际调优奠基基础。但仅有这些模糊概念是远远不够的,在理论和实践上还会做出其它总结,欢迎关注。

参考资料/推荐阅读

Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide 文中提到的资料,Oracle写的,具备官方指导意义

深刻理解Java虚拟机 推荐看第4-5章,详细讲解了调优工具的使用以及几个调优实例

高质量Java程序设计 推荐看第3章,做者用一个xml parser的例子给出了调优实战讲解,惋惜本书再也不再版,也未找到电子版

相关文章
相关标签/搜索