其实说到对JVM进行性能调优早已经是一个老生常谈的话题,若是你所在的技术团队还暂时达不到淘宝团队那样的高度,没法知足在OpenJDK的基础之上根据自身业务进行针对性的二次开发和定制调优,那么对于你来讲,惟一的选择就是尽量的熟悉JVM的内存布局,以及熟练掌握与GC相关的那些选项配置,不然JVM的基础性能调优不是痴人说梦?算法
相信对JVM有所了解的开发人员,对于调优过程当中牵扯的吞吐性、低延迟/高响应应该不会感受到陌生。既然生产环境中是大规模的分布式Java平台,JVM吃的内存必然不会太少。不知你们是否还曾记得,64位的JVM可以顺利访问大内存,其最主要的缘由是由于其采用了64位的指针架构,这同时也是寻址访问大内存的关键要素。而与之相反的32位的JVM的内存却被限定在了2-3GB上限(与操做平台密切相关,Linux平台,Windows则为1.5G上限)。 大规模的分布式Java平台除了JVM吃的内存特别大外(笔者以前的项目单点持有内存为30GB),为了增长每个节点的可用性,都是采用多JVM集群的部署模式,这样一来一旦发生单点故障的时候,不会致使整个服务不可用,从而也可以下降单点负载,提高总体程序的执行性能,更好的知足一些特定的高并发场景。 话说生产部署在服务器上的JVM大都是主动或者缺省选择server模式在奔跑,而且在Java7版本以后,JVM缺省开启了分层编译(Tiered Compilation)策略,由C1和C2编译器共同来执行本地代码的编译任务,C1编译器会对字节码进行简单和可靠的优化,以达到更快的编译速度,而C2编译器则会启动一些耗时更长的优化,以获取更好的本地代码编译质量。 那么对JVM进行性能调优的真正目的是什么呢?简单来讲就是为了知足程序的高吞吐量、低延迟/高响应性等需求。可是笔者不得不提醒你们,**调优是一个按部就班的过程,必然须要经历屡次迭代,最终才能换取一个较好的折中方案。**笔者在《Java虚拟机精讲》中曾经说起过,垃圾收集器中吞吐量和低延迟这两个目标实际上是存在相互竞争的矛盾,由于若是选择以吞吐量优先,那么下降内存回收的执行频率则是必然的,但这将会致使GC须要更长的暂停时间来执行内存回收。相反若是是选择以低延迟优先,那么为了下降每次执行内存回收时的暂停时间,只可以频繁地执行内存回收,但这又引发了新生代内存的缩减和致使程序吞吐量的降低。举个例子,在60s的JVM总运行时间里,每次GC的执行频率是20s/次,那么60s内一共会执行3次内存回收,按照每次GC耗时100ms来计算,最终一共会有300ms(即60/20*100
)被用于执行内存回收。可是若是咱们将选项“-XX:MaxGCPauseMillis”的值调小后,新生代的内存空间也会自动调整,相信你们都知道,内存空间越小就越容易被耗尽,那么GC的执行频率就会更频繁。以前在60s的JVM总运行时间里,最终会有300ms被用于执行内存回收,而现在GC的执行频率倒是10s/次,60s内将会执行6次内存回收,按照每次GC耗时80ms来计算,虽然看上去暂停时间更短了,但最终一共会有480ms(即60/10*80
)被用于执行内存回收,很明显程序的吞吐量降低了。所以,在JVM调优这个领域,没有任何一种调优方案是适用于全部应用场景的,同时,切勿极端才可以达到JVM性能调优的真正目的和意义。服务器
简而言之,总而言之,对JVM进行性能调优时,有2个基本原则你们须要进行理解。首先是尽量的让GC发生在新生代中,也就是尽量的多执行Minor GC,由于咱们都知道Full GC的执行频率尽管不会有Minor GC那么频繁,可是对程序响应性的影响是很是大的(笔者以前的项目Full GC诡异般的执行了50s,显然超出了对响应延迟的容忍度)。那么多让Minor GC执行,显然能够减小触发Full GC的频率。架构
其次,GC所持有的可用内存越大(Java Heap所占有的堆空间越大),GC的执行效率越好。这是由于内存越大,达到回收阈值就越不容易,那么明显能够提高程序的吞吐量和响应性。固然这并非说越大越好,若是一个项目JVM撑死只须要1-2G的运行内存,人傻钱多分配120G的内存量,或许程序在稳定状况下运行到硬件故障也不会发生一次Full GC。 既然内存并非越大越好,总有一个阈值。这就牵扯到生产环境中,开发人员究竟应该如何对Heap分配初始大小?其实这很简单,一个经历过严谨测试的项目,必然会在测试环境中测试N个周期才会移交至生产环境中进行部署,那么在测试环境中,咱们能够根据屡次迭代后观察Full GC的数据信息来估算生产环境中究竟应该给咱们的项目初始多大的内存空间。好比通过屡次迭代后,Full GC产生的数据信息中,若是老年代中的活跃数据占用内存大小为100m,那么按照通用的计算法则,能够按照约3-4倍的占用倍数来恒定生产环境中应该分配的堆大小(即-Xms和-Xmx),新生代和老年代的比例官方建议按照整个堆的3/8来进行分配,也就是说选项-Xmn能够占用整个堆内存空间的3/8,这是一种很是简单和通用的计算和分配方式。而永久代则能够按照Full GC后产生的数据信息,根据永久代活跃数据占用内存大小的1.5倍进行恒定生产环境中应该分配的初始值。 这里笔者稍微补充一下,在一些高并发场景下,尤为关注吞吐量和高响应的应用中,应该将-Xms和-Xmx设定为同一值,以此避免内存动态调整时产生的Full GC操做,永久代-XX:PermSize和-XX:MaxPermSize同理。并发
在HotSpot中,串行回收GC与并行回收GC是2个极端,在现在,更多人更倾向于选择后者,而且在一些极其注重吞吐量和高响应的应用场景下,并行回收有着串行回收没法比拟的绝对优点。因为堆空间中的对象大部分都是一些瞬时对象,所以这类对象的生命周期每每更可能是由新生代进行“控制”,以前也说过,尽量的让垃圾收集动做发生在新生代中,而不是Full GC。这样一来,对于新生代的性能调优就主要集中在几个问题上,首先是测量出Minor GC的执行平率和持续时间是否知足需求,以及-XX:ParallelGCThreads选项的配置。如图A-1所示:分布式
若是说Minor GC执行的太频繁,那么必然是-Xmn分配得太小,反之Minor GC好久才执行一次,而每次执行的周期较长,则意味着-Xmn分配得过大。那么究竟应该如何对新生代进行调优呢?简单来讲,咱们须要屡次迭代,从最初将-Xmn的值设置到最低,而后逐步微调,慢慢的你会发现Minor GC的执行频率在下降,直到最终知足需求便可中止。通过这样的调试,你会发现程序的吞吐量上来了,可是每次执行Minor GC的周期会变得较长,怎么办呢?咱们能够经过-XX:ParallelGCThreads选项调整GC执行的线程数,让更多的GC线程执行垃圾收集,提高GC的回收效率。这样一来,基本能够知足下降GC的回收平率,提高GC的回收效率。 因为使用的是并行GC,咱们能够充分利用多核CPU资源以及线程资源。同微调-Xmn选项同样,咱们首先能够将-XX:ParallelGCThreads设置为物理CPU核心数的1/2,好比你的CPU是6核,那么-XX:ParallelGCThreads的值就能够设置为3(最好不要小于2,不然将会影响并行GC的回收效率),这样一来,CPU可用资源就会将一半分配给GC线程使用,而剩下的CPU资源则服务于应用线程中。固然若是你的项目并不重视高响应,-XX:ParallelGCThreads的值能够相对的进行减小,以便于有更多的CPU资源分配给程序中的工做线程。高并发
新生代的调优若是你们都已经掌握,接下来咱们再来看老年代如何进行性能调优。尽管调优原则中笔者说起过,应该让垃圾收集动做尽量的发生在新生代中,也就是尽量多执行Minor GC,可是这并不表明程序永远不会执行Full GC,一旦程序触发Full GC时,所花费的时间每每要大于Minor GC的执行周期,若是Full GC执行的周期过长,对用户所带来的直观感觉是很是不友好的,好比用户在执行登陆操做,偏偏悲催的遇见JVM正在执行长时间的Full GC,请自行补白。。。 在GC的命令选项中并不存在直接设置来年代内存大小的选项,那么老年代的内存大小如何设置呢?简单来讲,老年代的内存空间大小间接等于-Xmx的值减去-Xmn的值,好比-Xmx为120G,-Xmn的值为45G,那么剩下的75G就是老年代的内存空间。在此你们须要注意,若是当-Xmn产生变化时,-Xmx也要随之成比例的发生变化,不然老年代占用的内存空间将会增大或变小,若是增大,Full GC的执行周期将会变得更长,反之执行频率将会频繁。 通常来讲,若是<=3G如下的堆内存,建议使用的GC组合是Parallel和Parallel Old,除非真的是需求没法容忍系统出现长时间的“Stop the World”(目前几乎没有任何一款GC不须要暂停工做线程,只是尽量的缩短暂停时间,包括G1)状况下,才推荐上CMS,不过通常大内存的使用,老年代首推CMS执行垃圾收集,而且CMS也是除G1以外的HotSpot中惟一的一款能够单独执行老年代增量回收,而没必要执行Full GC全量回收的垃圾收集器(Promotion Failed和Concurrent Mode Failed状况除外)。布局
之因此要用CMS,是由于CMS天生为低延迟/高响应而生。由于CMS的执行过程当中,只有初始标记和再次标记会出现暂停,而其它过程CMS的工做线程将会和程序的工做线程同时工做,大大提高了GC的回收效率。那么使用CMS一样须要进行优化,其中最主要的就是调整-Xmx的大小和-XX:CMSInitiatingOccupancyFraction选项。如图A-2所示:性能
-XX:CMSInitiatingOccupancyFraction用于设置老年代中的内存使用率达到多少百分比的时候执行内存回收(低版本的JDK缺省值为68%,JDK6及以上版本缺省值则为92%),在JDK6之后续版本中,若是按照缺省配置,当老年代的内存使用率达到92%后才进行垃圾收集,这每每会致使重新生代晋升到老年代中的对象将没法进行存放,若是-XX:CMSInitiatingOccupancyFraction设置得过低又会致使CMS GC触发的频率太快。通常来讲,在大内存的堆使用上,笔者将这个值设置在70-80之间算是比较合理的。 尽管CMS是大内存的首选,可是CMS仍然是有一些使人不满意的地方,好比抢占CPU资源、内存碎片等问题。不过总而言之,CMS目前在大内存的使用上,仍然是首选。测试