想给项目代码做做调优但有许多疑惑,比如有哪些参数要调、怎么调、使用什么工具、调优的效果如何定量测量等。发现Oracle的这份资料不错,简洁直接,回答了我的许多问题,给了许多很实用的大方向上的指导。将其中精华记录下来,希望能给同样入门的朋友一些启示。
Garbage Collectors垃圾收集器 (Garbage Collectors)是JVM中的内存管理工具。它的职责包括:
在年轻代为对象分配空间,并将存活比较久的对象移动到年老代;
在堆占用率超过某阈值时触发concurrent marking phase,在年老代找到存活的对象;
触发parallel copying,压缩活着的对象,释放垃圾空间。
看起来有点抽象,并且貌似没提到年轻代的垃圾收集,其实已经在第一条中提到了:“将存活比较久的对象移动到年老代”,这里隐含了对年轻代进行存活对象登记和收集的过程。简而言之,垃圾收集器的职责是:给对象分配内存;收集年轻代垃圾;收集年老代垃圾。
串/并行Garbage Collector的选择一般来说,JVM会根据系统的物理配置等因素选择一个默认的垃圾处理器。但显然,不同的应用程序有不同的行为(如使用内存的频率、对象的平均存活时间);也有不同的要求(如有界面的程序要求响应快速,而服务器端程序要求吞吐量高,能处理更多的请求)。所以,根据不同程序的特点,可能需要不同的垃圾处理器来管理。在此处,我们先从串/并行的角度浅浅地了解一下这个问题。
垃圾处理器可以粗略地分为串行进行和并行进行的,即垃圾处理这个过程在单线程还是多线程中进行;在Java SE 1.4之前的版本不支持并行。根据Amdahl's law (程序能够通过并行来加速的程度取决于程序中必须串行运行的部分),如果GC是串行进行的,则一个并行的应用程序的加速程度会受到GC的影响。假设我们通过增加处理器个数的方式来加速一个应用程序,那么随着处理器个数的增多,GC拖后腿的程度也越来越厉害,看下图:
GC时间所占的百分比随处理器个数的变化
这是一个数学模拟图,模拟了一个理想(完全并行)的应用程序的吞吐量受GC时间的影响。横轴代表处理器个数,纵轴代表吞吐量,不同颜色的曲线代表GC百分比不同的程序。红色曲线表示一条在单CPU下GC时间占1%的程序,在处理器个数增加到32个时,GC占整个程序运行时间的百分比竟超过了20%。
可以看到GC所占的时间百分比越大,拖后腿的程度就越厉害。这是一个很好理解的现象,因为GC是串行的,所以其运行时间不受处理器数量的影响。随着处理器的增多,应用程序本身的运行时间下降了,所以显得GC所占的时间百分比越来越大。
因此,在小系统上开发应用时可以忽略的一些GC小问题,当扩展到大型系统上时就会变得十分可观,甚至成为性能瓶颈。但是,此时在垃圾处理器上做一些小文章就有可能极大地增加性能。比如考虑到上图反映的现象,或许我们可以考虑换一个并行的垃圾处理器以提高吞吐量。
另一方面,小型应用如果不需要其他特殊的GC行为,通常使用串行垃圾处理器就够了,选择其他垃圾处理器可能反而会引入额外的复杂性和开销。
分代模型在处理垃圾时,需要先找到所有活着的对象,然后将剩下的作为垃圾进行处理。“找到所有活着的对象”这个过程需要耗费的时间与活着的对象数量成正比,这样的话,如果应用中本来就维护了大量的存活对象,那么找到活着的对象需要耗费大量的时间。为了优化这个过程,JVM程序员们基于一些经验提出了分代收集的思想。在这些经验中,最重要的是分代假设,即大部分对象都只存活很短的时间。
对象寿命的典型分布图
上图中,横轴代表总的字节分配数,即时间轴;纵轴代表不同时间下存活的对象所占字节数。左边的尖峰代表分配空间没多久就可以回收的对象,比如在某个loop中临时分配的对象,它们的寿命只有一个loop的时间;最右边代表存活很久的那些对象,比如初始化时就存在且一直活到程序结束的对象;在这两极之间,有一些用于中间计算的对象,即左边的尖峰右边的这个包。