如今的JVM运行Java程序(和其它的兼容性语言)时在高效性和稳定性方面作的很是出色。例如:自适应内存管理、垃圾收集、及时编译、动态类加载、锁优化等。虽然有了这种程度的自动化(或者说有这么多自动化),可是JVM仍然提供了足够多的外部监控和手动调优工具(容许命令行参数能够在JVM启动时传入到JVM中)。在有错误或低性能的状况下,JVM必须可以让调试,JVM提供了几百个这样的参数,因此若是没有这方面的知识很容易迷失。java
1)第一类包括了标准参数。顾名思义,标准参数中包括功能和输出的参数都是很稳定的,极可能在未来的JVM版本中不会改变。你能够用java命令(或者是用 java -help)检索出全部标准参数。例如:-server。web
2)第二类是X参数,非标准化的参数在未来的版本中可能会改变。全部的这类参数都以-X开始,而且能够用java -X来检索。注意,不能保证全部参数均可以被检索出来,其中就没有-Xcomp。算法
3)第三类是包含XX参数(到目前为止最多的),它们一样不是标准的,甚至很长一段时间内不被列出来。然而,在实际状况中X参数和XX参数并无什么不一样。X参数的功能是十分稳定的,然而不少XX参数仍在实验当中(主要是JVM的开发者用于debugging和调优JVM自身的实现)。值的一读的介绍非标准参数的文档 HotSpot JVM documentation,其中明确的指出XX参数不该该在不了解的状况下使用。这是真的,而且我认为这个建议一样适用于X参数(一样一些标准参数也是)。无论类别是什么,在使用参数以前应该先了解它可能产生的影响。
用一句话来讲明XX参数的语法。全部的XX参数都以”-XX:”开始,可是随后的语法不一样,取决于参数的类型。缓存
一、-server and -client
有两种类型的 HotSpot JVM,即”server”和”client”。服务端的VM中的默认为堆提供了一个更大的空间以及一个并行的垃圾收集器,而且在运行时能够更大程度地优化代码。客户端的VM更加保守一些(校对注:这里做者指客户端虚拟机有较小的默认堆大小),这样能够缩短JVM的启动时间和占用更少的内存。有一个叫”JVM功效学”的概念,它会在JVM启动的时候根据可用的硬件和操做系统来自动的选择JVM的类型。具体的标准能够在这里找到。从标准表中,咱们能够看到客户端的VM只在32位系统中可用。
注:虽然当初服务端VM的目标是长时间运行的服务进程,可是如今看来,在运行独立应用程序时它比客户端VM有更出色的性能安全
二、-version and -showversion服务器
当咱们调用“java”命令时,咱们如何才能知道咱们安装的是哪一个版本的Java和JVM类型呢?在同一个系统中安装多个Java,若是不注意的话有运行错误JVM的风险。在不一样的Linux版本上预装JVM这方面,我认可如今已经变的比之前好不少了。幸运的是,咱们如今能够使用-version参数,它能够打印出正在使用的JVM的信息。多线程
-version参数在打印完上述信息后当即终止JVM。还有一个相似的参数-showversion能够用来输出相同的信息,可是-showversion紧接着会处理并执行Java程序。所以,-showversion对几乎全部Java应用的命令行都是一个有效的补充。你永远不知道你何时,忽然须要了解一个特定的Java应用(崩溃时)使用的JVM的一些信息。在启动时添加-showversion,咱们就能保证当咱们须要时能够获得这些信息。并发
三、-Xint, -Xcomp, 和 -Xmixed
-Xint和-Xcomp参数和咱们的平常工做不是很相关,可是我很是有兴趣经过它来了解下JVM。在解释模式(interpreted mode)下,-Xint标记会强制JVM执行全部的字节码,固然这会下降运行速度,一般低10倍或更多。-Xcomp参数与它(-Xint)正好相反,JVM在第一次使用时会把全部的字节码编译成本地代码,从而带来最大程度的优化。这听起来不错,由于这彻底绕开了缓慢的解释器。然而,不少应用在使用-Xcomp也会有一些性能损失,固然这比使用-Xint损失的少,缘由是-xcomp没有让JVM启用JIT编译器的所有功能。JIT编译器在运行时建立方法使用文件,而后一步一步的优化每个方法,有时候会主动的优化应用的行为。这些优化技术,好比,积极的分支预测(optimistic branch prediction),若是不先分析应用就不能有效的使用。另外一方面方法只有证实它们与此相关时才会被编译,也就是,在应用中构建某种热点。被调用不多(甚至只有一次)的方法在解释模式下会继续执行,从而减小编译和优化成本。app
注:混合模式也有他本身的参数,-Xmixed。最新版本的HotSpot的默认模式是混合模式,因此咱们不须要特别指定这个标记。咱们来用对象填充HashMap而后检索它的结果作一个简单的用例。每个例子,它的运行时间都是不少次运行的平均时间jvm
理想的状况下,一个Java程序使用JVM的默认设置也能够运行得很好,因此通常来讲,没有必要设置任何JVM参数。然而,因为一些性能问题(很不幸的是,这些问题常常出现),一些相关的JVM参数知识会是咱们工做中得好伙伴。
全部已制定的HotSpot内存管理和垃圾回收算法都基于一个相同的堆内存划分:新生代(young generation)里存储着新分配的和较年轻的对象,老年代(old generation)里存储着长寿的对象。在此以外,永久代(permanent generation)存储着那些须要伴随整个JVM生命周期的对象,好比,已加载的对象的类定义或者String对象内部Cache。
一、-Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize)
-Xms和-Xmx能够说是最流行的JVM参数,它们能够容许咱们指定JVM的初始和最大堆内存大小。通常来讲,这两个参数的数值单位是Byte,但同时它们也支持使用速记符号,好比“k”或者“K”表明“kilo”,“m”或者“M”表明“mega”,“g”或者“G”表明“giga”。举个例子,下面的命令启动了一个初始化堆内存为128M,最大堆内存为2G,名叫“MyApp”的Java应用程序。java -Xms128m -Xmx2g MyApp
在实际使用过程当中,初始化堆内存的大小一般被视为堆内存大小的下界。然而JVM能够在运行时动态的调整堆内存的大小,因此理论上来讲咱们有可能会看到堆内存的大小小于初始化堆内存的大小。但一般咱们能够经过将“-Xms”和“-Xmx”设置为相同大小来得到一个固定大小的堆内存。
注:-Xms和-Xmx其实是-XX:InitialHeapSize和-XX:MaxHeapSize的缩写。咱们也能够直接使用这两个参数,它们所起得效果是同样的:
$ java -XX:InitialHeapSize=128m -XX:MaxHeapSize=2g MyApp
注:全部JVM关于初始\最大堆内存大小的输出都是使用它们的完整名称:“InitialHeapSize”和“InitialHeapSize”。因此当你查询一个正在运行的JVM的堆内存大小时,如使用-XX:+PrintCommandLineFlags参数或者经过JMX查询,你应该寻找“InitialHeapSize”和“InitialHeapSize”标志而不是“Xms”和“Xmx”。
二、-XX:+HeapDumpOnOutOfMemoryError and -XX:HeapDumpPath
若是咱们无法为-Xmx(最大堆内存)设置一个合适的大小,那么就有可能面临内存溢出(OutOfMemoryError)的风险,这多是咱们使用JVM时面临的最可怕的猛兽之一。一般来讲,分析堆内存快照(Heap Dump)是一个很好的定位手段,若是发生内存溢出时没有生成内存快照那就实在是太糟了,幸运的是,咱们能够经过设置-XX:+HeapDumpOnOutOfMemoryError 让JVM在发生内存溢出时自动的生成堆内存快照。默认状况下,堆内存快照会保存在JVM的启动目录下名为java_pid.hprof 的文件里(在这里就是JVM进程的进程号)。也能够经过设置-XX:HeapDumpPath=来改变默认的堆内存快照生成路径,能够是相对或者绝对路径。
注:堆内存快照文件可能很庞大,特别是当内存溢出错误发生的时候。所以,咱们推荐将堆内存快照生成路径指定到一个拥有足够磁盘空间的地方。
三、-XX:OnOutOfMemoryError
当内存溢发生时,咱们甚至能够能够执行一些指令,好比发个E-mail通知管理员或者执行一些清理工做。经过-XX:OnOutOfMemoryError 这个参数咱们能够作到这一点,这个参数能够接受一串指令和它们的参数。在下面的例子中,当内存溢出错误发生的时候,咱们会将堆内存快照写到/tmp/heapdump.hprof 文件而且在JVM的运行目录执行脚本cleanup.sh.
$ java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof -XX:OnOutOfMemoryError =”sh ~/cleanup.sh” MyApp
四、 -XX:PermSize and -XX:MaxPermSize
永久代在堆内存中是一块独立的区域,它包含了全部JVM加载的类的对象表示。为了成功运行应用程序,JVM会加载不少类(由于它们依赖于大量的第三方库,而这又依赖于更多的库而且须要从里面将类加载进来)这就须要增长永久代的大小。咱们能够使用-XX:PermSize 和-XX:MaxPermSize 来达到这个目的。其中-XX:MaxPermSize 用于设置永久代大小的最大值,-XX:PermSize 用于设置永久代初始大小。下面是一个简单的例子:
$ java -XX:PermSize=128m -XX:MaxPermSize=256m MyApp
注:这里设置的永久代大小并不会被包括在使用参数-XX:MaxHeapSize 设置的堆内存大小中。也就是说,经过-XX:MaxPermSize设置的永久代内存可能会须要由参数-XX:MaxHeapSize 设置的堆内存之外的更多的一些堆内存。
五、-XX:InitialCodeCacheSize and -XX:ReservedCodeCacheSize
JVM一个有趣的,但每每被忽视的内存区域是“代码缓存”,它是用来存储已编译方法生成的本地代码。代码缓存确实不多引发性能问题,可是一旦发生其影响多是毁灭性的。若是代码缓存被占满,JVM会打印出一条警告消息,并切换到interpreted-only 模式:JIT编译器被停用,字节码将再也不会被编译成机器码。所以,应用程序将继续运行,但运行速度会下降一个数量级,直到有人注意到这个问题。就像其余内存区域同样,咱们能够自定义代码缓存的大小。相关的参数是-XX:InitialCodeCacheSize 和-XX:ReservedCodeCacheSize,它们的参数和上面介绍的参数同样,都是字节值。
六、-XX:+UseCodeCacheFlushing
若是代码缓存不断增加,例如,由于热部署引发的内存泄漏,那么提升代码的缓存大小只会延缓其发生溢出。为了不这种状况的发生,咱们能够尝试一个有趣的新参数:当代码缓存被填满时让JVM放弃一些编译代码。经过使用-XX:+UseCodeCacheFlushing 这个参数,咱们至少能够避免当代码缓存被填满的时候JVM切换到interpreted-only 模式。不过,我仍建议尽快解决代码缓存问题发生的根本缘由,如找出内存泄漏并修复它。
单纯从JVM的功能考虑,并不须要新生代,彻底能够针对整个堆进行操做。新生代存在的惟一理由是优化垃圾回收(GC)的性能。更具体说,把堆划分为新生代和老年代有2个好处:简化了新对象的分配(只在新生代分配内存),能够更有效的清除再也不须要的对象(即死对象)(新生代和老年代使用不一样的GC算法)
经过普遍研究面向对象实现的应用,发现一个共同特色:不少对象的生存时间都很短。同时研究发现,新生对象不多引用生存时间长的对象。结合这2个特色,很明显 GC 会频繁访问新生对象,例如在堆中一个单独的区域,称之为新生代。在新生代中,GC能够快速标记回收”死对象”,而不须要扫描整个Heap中的存活一段时间的”老对象”。
SUN/Oracle 的HotSpot JVM 又把新生代进一步划分为3个区域:一个相对大点的区域,称为”伊甸园区(Eden)”;两个相对小点的区域称为”From 幸存区(survivor)”和”To 幸存区(survivor)”。按照规定,新对象会首先分配在 Eden 中(若是新对象过大,会直接分配在老年代中)。在GC中,Eden 中的对象会被移动到survivor中,直至对象知足必定的年纪(定义为熬过GC的次数),会被移动到老年代。
基于大多数新生对象都会在GC中被收回的假设,新生代的GC 使用复制算法。在GC前To 幸存区(survivor)保持清空,对象保存在 Eden 和 From 幸存区(survivor)中,GC运行时,Eden中的幸存对象被复制到 To 幸存区(survivor)。针对 From 幸存区(survivor)中的幸存对象,会考虑对象年龄,若是年龄没达到阀值(tenuring threshold),对象会被复制到To 幸存区(survivor)。若是达到阀值对象被复制到老年代。复制阶段完成后,Eden 和From 幸存区中只保存死对象,能够视为清空。若是在复制过程当中To 幸存区被填满了,剩余的对象会被复制到老年代中。最后 From 幸存区和 To幸存区会调换下名字,在下次GC时,To 幸存区会成为From 幸存区。
总结一下,对象通常出生在Eden区,年轻代GC过程当中,对象在2个幸存区之间移动,若是对象存活到适当的年龄,会被移动到老年代。当对象在老年代死亡时,就须要更高级别的GC,更重量级的GC算法(复制算法不适用于老年代,由于没有多余的空间用于复制)
如今应该能理解为何新生代大小很是重要了(译者,有另一种说法:新生代大小并不重要,影响GC的因素主要是幸存对象的数量),若是新生代太小,会致使新生对象很快就晋升到老年代中,在老年代中对象很难被回收。若是新生代过大,会发生过多的复制过程。咱们须要找到一个合适大小,不幸的是,要想得到一个合适的大小,只能经过不断的测试调优。这就须要JVM参数了
一、-XX:NewSize and -XX:MaxNewSize
就像能够经过参数(-Xms and -Xmx) 指定堆大小同样,能够经过参数指定新生代大小。设置 XX:MaxNewSize 参数时,应该考虑到新生代只是整个堆的一部分,新生代设置的越大,老年代区域就会减小。通常不容许新生代比老年代还大,由于要考虑GC时最坏状况,全部对象都晋升到老年代。(译者:会发生OOM错误) -XX:MaxNewSize 最大能够设置为-Xmx/2 .
考虑性能,通常会经过参数 -XX:NewSize 设置新生代初始大小。若是知道新生代初始分配的对象大小(通过监控) ,这样设置会有帮助,能够节省新生代自动扩展的消耗。
二、-XX:NewRatio
能够设置新生代和老年代的相对大小。这种方式的优势是新生代大小会随着整个堆大小动态扩展。参数 -XX:NewRatio 设置老年代与新生代的比例。例如 -XX:NewRatio=3 指定老年代/新生代为3/1. 老年代占堆大小的 3/4 ,新生代占 1/4 .
若是针对新生代,同时定义绝对值和相对值,绝对值将起做用。下面例子:
$ java -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=3 MyApp
以上设置, JVM 会尝试为新生代分配四分之一的堆大小,但不会小于32MB或大于521MB
在设置新生代大小问题上,使用绝对值仍是相对值,不存在通用准则 。若是了解应用的内存使用状况,设置固定大小的堆和新生代更有利,固然也能够设置相对值。若是对应用的内存使用一无所知,正确的作法是不要设置任何参数,若是应用运行良好。很好,咱们不用作任何额外动做.若是遇到性能或OutOfMemoryErrors, 在调优以前,首先须要进行一系列有目的的监控测试,缩小问题的根源。
三、-XX:SurvivorRatio
参数 -XX:SurvivorRatio 与 -XX:NewRatio 相似,做用于新生代内部区域。-XX:SurvivorRatio 指定伊甸园区(Eden)与幸存区大小比例. 例如, -XX:SurvivorRatio=10 表示伊甸园区(Eden)是 幸存区To 大小的10倍(也是幸存区From的10倍).因此,伊甸园区(Eden)占新生代大小的10/12, 幸存区From和幸存区To 每一个占新生代的1/12 .注意,两个幸存区永远是同样大的..
设定幸存区大小有什么做用? 假设幸存区相对伊甸园区(Eden)过小, 相应新生对象的伊甸园区(Eden)永远很大空间, 咱们固然但愿,若是这些对象在GC时所有被回收,伊甸园区(Eden)被清空,一切正常.然而,若是有一部分对象在GC中幸存下来, 幸存区只有不多空间容纳这些对象.结果大部分幸存对象在一次GC后,就会被转移到老年代 ,这并非咱们但愿的.考虑相反状况, 假设幸存区相对伊甸园区(Eden)太大,固然有足够的空间,容纳GC后的幸存对象. 可是太小的伊甸园区(Eden),意味着空间将越快耗尽,增长新生代GC次数,这是不可接受的。
总之,咱们但愿最小化短命对象晋升到老年代的数量,同时也但愿最小化新生代GC 的次数和持续时间.咱们须要找到针对当前应用的折中方案, 寻找适合方案的起点是 了解当前应用中对象的年龄分布状况。
四、-XX:+PrintTenuringDistribution
参数 -XX:+PrintTenuringDistribution 指定JVM 在每次新生代GC时,输出幸存区中对象的年龄分布。例如:
Desired survivor size 75497472 bytes, new threshold 15 (max 15)
第一行说明幸存区To大小为 75 MB. 也有关于老年代阀值(tenuring threshold)的信息, 老年代阀值,意思是对象重新生代移动到老年代以前,通过几回GC(即, 对象晋升前的最大年龄). 上例中,老年代阀值为15,最大也是15.
以后行表示,对于小于老年代阀值的每个对象年龄,本年龄中对象所占字节 (若是当前年龄没有对象,这一行会忽略). 上例中,一次 GC 后幸存对象大约 19 MB, 两次GC 后幸存对象大约79 KB , 三次GC 后幸存对象大约 3 MB .每行结尾,显示直到本年龄所有对象大小.因此,最后一行的 total 表示幸存区To 总共被占用22 MB . 幸存区To 总大小为 75 MB ,当前老年代阀值为15,能够判定在本次GC中,没有对象会移动到老年代。如今假设下一次GC 输出为:
Desired survivor size 75497472 bytes, new threshold 2 (max 15)
对比前一次老年代分布。明显的,年龄2和年龄3 的对象还保持在幸存区中,由于咱们看到年龄3和4的对象大小与前一次年龄2和3的相同。同时发现幸存区中,有一部分对象已经被回收,由于本次年龄2的对象大小为 12MB ,而前一次年龄1的对象大小为 19 MB。最后能够看到最近的GC中,有68 MB 新对象,从伊甸园区移动到幸存区。
注意,本次GC 幸存区占用总大小 84 MB -大于75 MB. 结果,JVM 把老年代阀值从15下降到2,在下次GC时,一部分对象会强制离开幸存区,这些对象可能会被回收(若是他们恰好死亡)或移动到老年代。
五、-XX:InitialTenuringThreshold, -XX:MaxTenuringThreshold and -XX:TargetSurvivorRatio
参数 -XX:+PrintTenuringDistribution 输出中的部分值能够经过其它参数控制。经过 -XX:InitialTenuringThreshold 和 -XX:MaxTenuringThreshold 能够设定老年代阀值的初始值和最大值。另外,能够经过参数 -XX:TargetSurvivorRatio 设定幸存区的目标使用率.例如 , -XX:MaxTenuringThreshold=10 -XX:TargetSurvivorRatio=90 设定老年代阀值的上限为10,幸存区空间目标使用率为90%。
有多种方式,设置新生代行为,没有通用准则。咱们必须清楚如下2中状况:
1. 若是从年龄分布中发现,有不少对象的年龄持续增加,在到达老年代阀值以前。这表示 -XX:MaxTenuringThreshold 设置过大 2. 若是 -XX:MaxTenuringThreshold 的值大于1,可是不少对象年龄从未大于1.应该看下幸存区的目标使用率。若是幸存区使用率从未到达,这表示对象都被GC回收,这正是咱们想要的。 若是幸存区使用率常常达到,有些年龄超过1的对象被移动到老年代中。这种状况,能够尝试调整幸存区大小或目标使用率。
六、-XX:+NeverTenure and -XX:+AlwaysTenure
最后,咱们介绍2个颇为少见的参数,对应2种极端的新生代GC状况.设置参数 -XX:+NeverTenure , 对象永远不会晋升到老年代.当咱们肯定不须要老年代时,能够这样设置。这样设置风险很大,而且会浪费至少一半的堆内存。相反设置参数 -XX:+AlwaysTenure, 表示没有幸存区,全部对象在第一次GC时,会晋升到老年代。
没有合理的场景使用这个参数。能够在测试环境中,看下这样设置会发生什么有趣的事.可是并不推荐使用这些参数.
结论
适当的配置新生代很是重要,有至关多的参数能够设置新生代。然而,单独调整新生代,而不考虑老年代是不可能优化成功的。当调整堆和GC设置时,咱们老是应该同时考虑新生代和老年代。
在实践中咱们发现对于大多数的应用领域,评估一个垃圾收集(GC)算法如何根据以下两个标准:
首先让咱们来明确垃圾收集(GC)中的两个术语:吞吐量(throughput)和暂停时间(pause times)。 JVM在专门的线程(GC threads)中执行GC。 只要GC线程是活动的,它们将与应用程序线程(application threads)争用当前可用CPU的时钟周期。 简单点来讲:
高吞吐量最好由于这会让应用程序的最终用户感受只有应用程序线程在作“生产性”工做。 低暂停时间最好由于从最终用户的角度来看无论是GC仍是其余缘由致使一个应用被挂起始终是很差的。 这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停均可能打断终端用户体验。 所以,具备低的最大暂停时间是很是重要的,特别是对于一个交互式应用程序。不幸的是”高吞吐量”和”低暂停时间”是一对相互竞争的目标(矛盾)。对于年老代,HotSpot虚拟机提供两类垃圾收集算法(除了新的G1垃圾收集算法):
当年老代中因为缺少空间致使对象分配失败时会触发垃圾收集器(事实上,”分配”的一般是指从年轻代提高到年老代的对象)。 从所谓的”GC根”(GC roots)开始,搜索堆中的可达对象并将其标记为活着的,以后,垃圾收集器将活着的对象移到年老代的一块无碎片(non-fragmented)内存块中,并标记剩余的内存空间是空闲的。 也就是说,咱们不像复制策略那样移到一个不一样的堆区域,像年轻代垃圾收集算法所作的那样。 相反地,咱们把全部的对象放在一个堆区域中,从而对该堆区域进行碎片整理。 垃圾收集器使用一个或多个线程来执行垃圾收集。 当使用多个线程时,算法的不一样步骤被分解,使得每一个收集线程大多时候工做在本身的区域而不干扰其余线程。 在垃圾收集期间,全部的应用程序线程暂停,只有垃圾收集完成以后才会从新开始。 如今让咱们来看看跟面向吞吐量垃圾收集算法有关的重要JVM配置参数。
一、-XX:+UseSerialGC
咱们使用该标志来激活串行垃圾收集器,例如单线程面向吞吐量垃圾收集器。 不管年轻代仍是年老代都将只有一个线程执行垃圾收集。 该标志被推荐用于只有单个可用处理器核心的JVM。 在这种状况下,使用多个垃圾收集线程甚至会拔苗助长,由于这些线程将争用CPU资源,形成同步开销,却从未真正并行运行。
二、-XX:+UseParallelGC
有了这个标志,咱们告诉JVM使用多线程并行执行年轻代垃圾收集。 在我看来,Java 6中不该该使用该标志由于-XX:+UseParallelOldGC显然更合适。 须要注意的是Java 7中该状况改变了一点(详见本概述),就是-XX:+UseParallelGC能达到-XX:+UseParallelOldGC同样的效果。
三、-XX:+UseParallelOldGC
该标志的命名有点不巧,由于”老”听起来像”过期”。 然而,”老”其实是指年老代,这也解释了为何-XX:+UseParallelOldGC要优于-XX:+UseParallelGC:除了激活年轻代并行垃圾收集,也激活了年老代并行垃圾收集。 当指望高吞吐量,而且JVM有两个或更多可用处理器核心时,我建议使用该标志。
做为旁注,HotSpot的并行面向吞吐量垃圾收集算法一般称为”吞吐量收集器”,由于它们旨在经过并行执行来提升吞吐量。
四、-XX:ParallelGCThreads
经过-XX:ParallelGCThreads=咱们能够指定并行垃圾收集的线程数量。 例如,-XX:ParallelGCThreads=6表示每次并行垃圾收集将有6个线程执行。 若是不明确设置该标志,虚拟机将使用基于可用(虚拟)处理器数量计算的默认值。 决定因素是由Java Runtime。availableProcessors()方法的返回值N,若是N<=8,并行垃圾收集器将使用N个垃圾收集线程,若是N>8个可用处理器,垃圾收集线程数量应为3+5N/8。
当JVM独占地使用系统和处理器时使用默认设置更有意义。 可是,若是有多个JVM(或其余耗CPU的系统)在同一台机器上运行,咱们应该使用-XX:ParallelGCThreads来减小垃圾收集线程数到一个适当的值。 例如,若是4个以服务器方式运行的JVM同时跑在在一个具备16核处理器的机器上,设置-XX:ParallelGCThreads=4是明智的,它能使不一样JVM的垃圾收集器不会相互干扰。
五、-XX:-UseAdaptiveSizePolicy
吞吐量垃圾收集器提供了一个有趣的(但常见,至少在现代JVM上)机制以提升垃圾收集配置的用户友好性。 这种机制被看作是HotSpot在Java 5中引入的”人体工程学”概念的一部分。 经过人体工程学,垃圾收集器能将堆大小动态变更像GC设置同样应用到不一样的堆区域,只要有证据代表这些变更将能提升GC性能。 “提升GC性能”的确切含义能够由用户经过-XX:GCTimeRatio和-XX:MaxGCPauseMillis(见下文)标记来指定。
重要的是要知道人体工程学是默认激活的。 这很好,由于自适应行为是JVM最大优点之一。 不过,有时咱们须要很是清楚对于特定应用什么样的设置是最合适的,在这些状况下,咱们可能不但愿JVM混乱咱们的设置。 每当咱们发现处于这种状况时,咱们能够考虑经过-XX:-UseAdaptiveSizePolicy停用一些人体工程学。
六、-XX:GCTimeRatio
经过-XX:GCTimeRatio=咱们告诉JVM吞吐量要达到的目标值。 更准确地说,-XX:GCTimeRatio=N指定目标应用程序线程的执行时间(与总的程序执行时间)达到N/(N+1)的目标比值。 例如,经过-XX:GCTimeRatio=9咱们要求应用程序线程在整个执行时间中至少9/10是活动的(所以,GC线程占用其他1/10)。 基于运行时的测量,JVM将会尝试修改堆和GC设置以期达到目标吞吐量。 -XX:GCTimeRatio的默认值是99,也就是说,应用程序线程应该运行至少99%的总执行时间。
七、-XX:MaxGCPauseMillis
经过-XX:GCTimeRatio=告诉JVM最大暂停时间的目标值(以毫秒为单位)。 在运行时,吞吐量收集器计算在暂停期间观察到的统计数据(加权平均和标准误差)。 若是统计代表正在经历的暂停其时间存在超过目标值的风险时,JVM会修改堆和GC设置以下降它们。 须要注意的是,年轻代和年老代垃圾收集的统计数据是分开计算的,还要注意,默认状况下,最大暂停时间没有被设置。
若是最大暂停时间和最小吞吐量同时设置了目标值,实现最大暂停时间目标具备更高的优先级。 固然,没法保证JVM将必定能达到任一目标,即便它会努力去作。 最后,一切都取决于手头应用程序的行为。
当设置最大暂停时间目标时,咱们应注意不要选择过小的值。 正如咱们如今所知道的,为了保持低暂停时间,JVM须要增长GC次数,那样可能会严重影响可达到的吞吐量。 这就是为何对于要求低暂停时间做为主要目标的应用程序(大多数是Web应用程序),我会建议不要使用吞吐量收集器,而是选择CMS收集器。 CMS收集器是本系列下一部分的主题。
补充:
并发和并行从宏观上来说都是同时处理多路请求的概念。但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。(但任一个时刻点上只有一个程序在处理机上运行。)
HotSpot JVM的并发标记清理收集器(CMS收集器)的主要目标就是:低应用停顿时间。该目标对于大多数交互式应用很重要,好比web应用。就像吞吐量收集器,CMS收集器处理老年代的对象,然而其操做要复杂得多。吞吐量收集器老是暂停应用程序线程,而且多是至关长的一段时间,然而这可以使该算法安全地忽略应用程序。相比之下,CMS收集器被设计成在大多数时间能与应用程序线程并行执行,仅仅会有一点(短暂的)停顿时间。GC与应用程序并行的缺点就是,可能会出现各类同步和数据不一致的问题。为了实现安全且正确的并发执行,CMS收集器的GC周期被分为了好几个连续的阶段。
1)CMS收集器的过程:
CMS收集器的GC周期由6个阶段组成。其中4个阶段(名字以Concurrent开始的)与实际的应用程序是并发执行的,而其余2个阶段须要暂停应用程序线程。
应该指出,尽管CMS收集器为老年代垃圾回收提供了几乎彻底并发的解决方案,然而年轻代仍然经过“stop-the-world”方法来进行收集。对于交互式应用,停顿也是可接受的,背后的原理是年轻带的垃圾回收时间一般是至关短的。
2)挑战:
当咱们在真实的应用中使用CMS收集器时,咱们会面临两个主要的挑战,可能须要进行调优:
第二个挑战就是应用的对象分配率高。若是获取对象实例的频率高于收集器清除堆里死对象的频率,并发算法将再次失败。从某种程度上说,老年代将没有足够的可用空间来容纳一个从年轻代提高过来的对象。这种状况被称为“并发模式失败”,而且JVM会执行堆碎片整理:触发Full GC。
当这些情形之一出如今实践中时(常常会出如今生产系统中),常常被证明是老年代有大量没必要要的对象。
下面我看看大多数与CMS收集器调优相关的JVM标志参数。
一、-XX:+UseConcMarkSweepGC
该标志首先是激活CMS收集器。默认HotSpot JVM使用的是并行收集器。
二、-XX:UseParNewGC
当使用CMS收集器时,该标志激活年轻代使用多线程并行执行垃圾回收。这使人很惊讶,咱们不能简单在并行收集器中重用-XX:UserParNewGC标志,由于概念上年轻代用的算法是同样的。然而,对于CMS收集器,年轻代GC算法和老年代GC算法是不一样的,所以年轻代GC有两种不一样的实现,而且是两个不一样的标志。
注:最新的JVM版本,当使用-XX:+UseConcMarkSweepGC时,-XX:UseParNewGC会自动开启。所以,若是年轻代的并行GC不想开启,能够经过设置-XX:-UseParNewGC来关掉。
三、-XX:+CMSConcurrentMTEnabled
当该标志被启用时,并发的CMS阶段将以多线程执行(所以,多个GC线程会与全部的应用程序线程并行工做)。该标志已经默认开启,若是顺序执行更好,这取决于所使用的硬件,多线程执行能够经过-XX:-CMSConcurremntMTEnabled禁用。
四、-XX:ConcGCThreads
标志-XX:ConcGCThreads=(早期JVM版本也叫-XX:ParallelCMSThreads)定义并发CMS过程运行时的线程数。好比value=4意味着CMS周期的全部阶段都以4个线程来执行。尽管更多的线程会加快并发CMS过程,但其也会带来额外的同步开销。所以,对于特定的应用程序,应该经过测试来判断增长CMS线程数是否真的可以带来性能的提高。
若是还标志未设置,JVM会根据并行收集器中的-XX:ParallelGCThreads参数的值来计算出默认的并行CMS线程数。该公式是ConcGCThreads = (ParallelGCThreads + 3)/4。所以,对于CMS收集器, -XX:ParallelGCThreads标志不只影响“stop-the-world”垃圾收集阶段,还影响并发阶段。
总之,有很多方法能够配置CMS收集器的多线程执行。正是因为这个缘由,建议第一次运行CMS收集器时使用其默认设置, 而后若是须要调优再进行测试。只有在生产系统中测量(或类生产测试系统)发现应用程序的暂停时间的目标没有达到 , 就能够经过这些标志应该进行GC调优。
五、-XX:CMSInitiatingOccupancyFraction
当堆满以后,并行收集器便开始进行垃圾收集,例如,当没有足够的空间来容纳新分配或提高的对象。对于CMS收集器,长时间等待是不可取的,由于在并发垃圾收集期间应用持续在运行(而且分配对象)。所以,为了在应用程序使用完内存以前完成垃圾收集周期,CMS收集器要比并行收集器更先启动。
由于不一样的应用会有不一样对象分配模式,JVM会收集实际的对象分配(和释放)的运行时数据,而且分析这些数据,来决定何时启动一次CMS垃圾收集周期。为了引导这一过程, JVM会在一开始执行CMS周期前做一些线索查找。该线索由 -XX:CMSInitiatingOccupancyFraction=来设置,该值表明老年代堆空间的使用率。好比,value=75意味着第一次CMS垃圾收集会在老年代被占用75%时被触发。一般CMSInitiatingOccupancyFraction的默认值为68(以前很长时间的经从来决定的)。
六、-XX:+UseCMSInitiatingOccupancyOnly
咱们用-XX+UseCMSInitiatingOccupancyOnly标志来命令JVM不基于运行时收集的数据来启动CMS垃圾收集周期。而是,当该标志被开启时,JVM经过CMSInitiatingOccupancyFraction的值进行每一次CMS收集,而不只仅是第一次。然而,请记住大多数状况下,JVM比咱们本身能做出更好的垃圾收集决策。所以,只有当咱们充足的理由(好比测试)而且对应用程序产生的对象的生命周期有深入的认知时,才应该使用该标志。
七、-XX:+CMSClassUnloadingEnabled
相对于并行收集器,CMS收集器默认不会对永久代进行垃圾回收。若是但愿对永久代进行垃圾回收,可用设置标志-XX:+CMSClassUnloadingEnabled。在早期JVM版本中,要求设置额外的标志-XX:+CMSPermGenSweepingEnabled。注意,即便没有设置这个标志,一旦永久代耗尽空间也会尝试进行垃圾回收,可是收集不会是并行的,而再一次进行Full GC。
八、-XX:+CMSIncrementalMode
该标志将开启CMS收集器的增量模式。增量模式常常暂停CMS过程,以便对应用程序线程做出彻底的让步。所以,收集器将花更长的时间完成整个收集周期。所以,只有经过测试后发现正常CMS周期对应用程序线程干扰太大时,才应该使用增量模式。因为现代服务器有足够的处理器来适应并发的垃圾收集,因此这种状况发生得不多。
九、-XX:+ExplicitGCInvokesConcurrent and -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
现在,被普遍接受的最佳实践是避免显式地调用GC(所谓的“系统GC”),即在应用程序中调用system.gc()。然而,这个建议是无论使用的GC算法的,值得一提的是,当使用CMS收集器时,系统GC将是一件很不幸的事,由于它默认会触发一次Full GC。幸运的是,有一种方式能够改变默认设置。标志-XX:+ExplicitGCInvokesConcurrent命令JVM不管何时调用系统GC,都执行CMS GC,而不是Full GC。第二个标志-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses保证当有系统GC调用时,永久代也被包括进CMS垃圾回收的范围内。所以,经过使用这些标志,咱们能够防止出现意料以外的”stop-the-world”的系统GC。
十、-XX:+DisableExplicitGC
然而在这个问题上…这是一个很好提到- XX:+ DisableExplicitGC标志的机会,该标志将告诉JVM彻底忽略系统的GC调用(无论使用的收集器是什么类型)。对于我而言,该标志属于默认的标志集合中,能够安全地定义在每一个JVM上运行,而不须要进一步思考。