想给项目代码做做调优但有许多疑惑,比如有哪些参数要调、怎么调、使用什么工具、调优的效果如何定量测量等。发现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的时间;最右边代表存活很久的那些对象,比如初始化时就存在且一直活到程序结束的对象;在这两极之间,有一些用于中间计算的对象,即左边的尖峰右边的这个包。

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

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

除了串行收集器和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的例子给出了调优实战讲解,可惜本书不再再版,也未找到电子版

GC调优入门笔记的更多相关文章

  1. Java GC 专家系列3:GC调优实践

    本篇是”GC专家系列“的第三篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.所以,你应该已经了解了JDK 7中的5种GC类型,以及每种G ...

  2. GC参考手册 —— GC 调优(基础篇)

    GC调优(Tuning Garbage Collection)和其他性能调优是同样的原理.初学者可能会被 200 多个 GC参数弄得一头雾水, 然后随便调整几个来试试结果,又或者修改几行代码来测试.其 ...

  3. GC参考手册 —— GC 调优(工具篇)

    JVM 在程序执行的过程中, 提供了GC行为的原生数据.那么, 我们就可以利用这些原生数据来生成各种报告.原生数据(raw data) 包括: 各个内存池的当前使用情况, 各个内存池的总容量, 每次G ...

  4. 性能测试系列-java gc调优

    性能测试中除了需要做好性能测试外,我们还需要做性能测试后的,性能调优,需要发现性能问题,也需要做性能调优,在做性能调优中,jvm的性能调优是经常遇到的一个. 随着jdk版本的迅速变化,jdk里面的GC ...

  5. GC调优

    Gc调优的目标:1.降低停顿时间 2.提高吞吐量 3.避免full-gc 调优可以使用的手段:1.各个内存区的大小调整:堆,年轻代,老年代,方法区等等2.减少短暂对象的存活时间,提高长期对象的复用率( ...

  6. GC调优在Spark应用中的实践(转载)

    Spark是时下非常热门的大数据计算框架,以其卓越的性能优势.独特的架构.易用的用户接口和丰富的分析计算库,正在工业界获得越来越广泛的应用.与Hadoop.HBase生态圈的众多项目一样,Spark的 ...

  7. GC调优在Spark应用中的实践[转]

    作者:仲浩   出处:<程序员>电子刊5月B   摘要:Spark立足内存计算,常常需要在内存中存放大量数据,因此也更依赖JVM的垃圾回收机制.与此同时,它也兼容批处理和流式处理,对于程序 ...

  8. gc调优我们到底在调整什么

    java开发一般都会涉及到jvm调优其中gc调优是个重点项.那gc调优调整的究竟是什么呢准确来说是业务.下面围绕这个话题展开 起因 为什么说是业务呢得从cc++开始说起如果说是用c/c++做开发运行的 ...

  9. 深入JVM系列(二)之GC机制、收集器与GC调优

    一.回想JVM内存分配 须要了解很多其它内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配 2.大对象直接进入老年代  3.长期存活的 ...

随机推荐

  1. c++预声明类引发的无法解析外部符号问题

    在VisualStudio下开发C++程序常遇到链接问题就是:LNK2019 无法解析外部符号. 这个问题一般我们认为是没有将引用的代码链接到当前项目造成,也有例外,就是下面我要说的预声明类导致的. ...

  2. [转] vi/vim命令模式和编辑模式各种操作

    摘要:vi 编辑器是最常用的文档创建和编辑工具,初学者应该学会简单应用vi ,学会在vi 中做简单的修改.删除.插入.搜索及替换作业:如果您是新手,不妨看看本文,或许这篇文档能让您在最短的时间内学会v ...

  3. ORACLE获取SQL绑定变量值的方法总结

      本文总结一下ORACLE数据库中如何获取SQL绑定变量值的方法,在SQL优化调优过程中,经常会用到这方面的知识点.在此梳理.总结一下,方面日后查找.翻阅. 方法1:查询V$SQL V$SQL视图中 ...

  4. ASP.NET MVC概述及第一个MVC程序

    一.ASP.NET 概述        1. .NET Framework 与 ASP.NET                .NET Framework包含两个重要组件:.NET Framework ...

  5. Linux Rsyslog日志集中管理

    Linux Rsyslog日志集中管理 一.Rsyslog简介 ryslog 是一个快速处理收集系统日志的程序,提供了高性能.安全功能和模块化设计.rsyslog 是syslog 的升级版,它将多种来 ...

  6. LeetCode算法题-Lowest Common Ancestor of a Binary Search Tree

    这是悦乐书的第197次更新,第203篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第59题(顺位题号是235).给定二叉搜索树(BST),找到BST中两个给定节点的最低共 ...

  7. Angular中ui-router实现路由嵌套案例

    学习 ui-router 资料整理 对于Angular内置的路由是单路由视图,ui-router可以实现路由嵌套.后面将会有一个案例概括前面所有资料整理 学习 ui-router - 管理状态  ht ...

  8. 【Linux基础】VI命令模式下删除拷贝与粘贴

    在VI命令模式下:y 表示拷贝, d 表示删除,p标识粘贴 1.删除 dw 表示删除从当前光标到光标所在单词结尾的内容. d0 表示删除从当前光标到光标所在行首的内容. d$ 表示删除从当前光标到光标 ...

  9. linux历史命令查找快捷方式

      一.回到上次操作的目录# cd -进入上次访问目录 二.历史命令搜索操作快捷键:[Ctrl + r], [Ctrl + p], [Ctrl + n] 在终端中按捉 [Ctrl] 键的同时 [r] ...

  10. 【HNOI2018】排列

    [HNOI2018]排列 神仙贪心题. 题目说这么大堆东西就是想告诉你这是个森林,选了\(v\)的父亲后才能选\(v\). 我们设\(w_v\)为\(v\)所在联通块权值和,\(size_v\)表示\ ...