JVM调优

1        理论篇

1.1     多功能养鱼塘-JVM内存

大鱼塘O可分配内存): JVM能够调度使用的总的内存数,这个数量受操做系统进程寻址范围、系统虚拟内存总数、系统物理内存总数、其余系统运行所占用的内存资源等因素的制约。java

小池塘A(堆内存):JVM运行时数据区域,它为类实例和数组分配的内存。堆能够是固定大小的也能够是可变大小的。其中 Heap = {Old + NEW = { Eden , from, to } }。程序员

小池塘B(非堆内存):包括全部线程之间共享的一个方法区域和JVM为优化或内部处理所分配的内存。它存储每个类的结构,如一个运行时的常量池、字段和方法数据、方法的代码和构造函数。这个方法区是逻辑上堆的一部分,但依赖于实现,一个JVM能够不去回收或者压缩它。像堆同样,方法区能够固定大小的,也能够是大小可变的。方法区不是必须是连续的,它们能够是不连续的。除方法区以外,JVM老是从非堆中分配用于优化和内部处理所需的内存。例如,JIT编译器为高性能的JVM代码转换存储成本地代码而分配的内存。web

整个池塘结构图以下:算法

 

 

查看大池塘O大小的方法为:数据库

在命令行下用 java -XmxXXXXM -version 命令来进行测试,而后逐渐的增大XXXX的值,若是执行正常就表示指定的内存大小可用,不然会打印错误信息,示例以下:windows

java -Xmx3072M -version。数组

当一个URL被访问时,内存申请过程以下:浏览器

A. JVM会试图为相关Java对象在Eden中初始化一块内存区域缓存

B. 当Eden空间足够时,内存申请结束。不然到下一步服务器

C. JVM试图释放在Eden中全部不活跃的对象(这属于1或更高级的垃圾回收), 释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区

D. Survivor区被用来做为Eden及OLD的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,不然会被保留在Survivor区

E. 当OLD区空间不够时,JVM会在OLD区进行彻底的垃圾收集(0级)

F. 彻底垃圾收集后,若Survivor及OLD区仍然没法存放从Eden复制过来的部分对象,致使JVM没法在Eden区为新对象建立内存区域,则出现”out of memory错误”

1.2     池塘中的鱼-程序中的对象

程序中运行的各类类实例称之为对象,每一个对象都有不一样的生命周期,有的存活时间长点,有的存活时间短点,这就想鱼塘中养的不一样生长期的鱼同样,有的三个月就能够上市,有的鱼则须要6个月甚至更长的时间才能上市。JVM内存机制的设置就是为了要知足这种不一样生命周期的对象对内存的需求,并使之能达到最大的性能表现。

1.3     养殖区域划分-JVM中的代

鱼塘主人为了充分利用现有的条件来赚取更多的利润,他须要喂养各类不一样种类的鱼,因而又把鱼塘分割成了几块不一样区域:“鱼苗养殖区”、“短中期养殖区”、“长期养殖区”,来养殖不一样生长周期的鱼。JVM一样为了对各类不一样生命周期的对象进行有效管理也划分了各类不一样的区域,这就是“代”的概念,分别叫作:“青年代”、“老年代”、“持久代”,下面逐一介绍每一个代的含义和做用。

短中期鱼苗养殖区-年青代(Young Generation)

年青代由一个Eden Space和两个Survivor Spaces组成,虚拟机初始时分配全部的对象到Eden Space,许多对象也是在这里死去。当它执行一个“minor GC”的时候,虚拟机将从Eden Space中移动一些残余的对象到其中的一个Survivor Spaces中。青年代就好像养鱼塘中的“中短时间养殖区”同样,主人把鱼先投放到“短时间养殖区”喂养,隔一段时间就开始下网捞出已经长成的那些鱼拿到集市去卖,这个过程就是从“Eden Space”中执行垃圾回收的过程。主人接着把捕捞以后剩下的“漏网之鱼”赶到“中期养殖区”继续喂养。这个“中期养殖区”就是“Survivor Spaces”,固然鱼在“中期养殖区”喂养一段时间后也要捞出那些长成的鱼去卖,这就是对“Survivor Spaces” 执行垃圾回收的过程。

Ps Eden Space: 这个内存池在对象初始化时被分配;

Ps Survivor Space: 这个内存池中包含着Eden Space 通过GC以后幸存下来的对象;

年轻代设置策略:对于响应时间优先的应用需尽量设大,直到接近系统的最低响应时间限制(根据实际状况选择)。在此种状况下,年轻代收集发生的频率也是最小的。同时,减小到达老年代的对象。对于吞吐量优先的应用则尽量的设置大,可达到Gbit的程度。由于对响应时间没有要求,垃圾收集能够并行进行,通常适合8CPU以上的应用。

长期养殖区-老年代(老年代):、

虚拟机将在Survivor Spaces中生存足够长时间的对象移动到老年代的Tenured Spaces中。当Tenured Generation被填满,则将执行一个彻底GC,这个彻底GC很是的慢,由于它要处理全部存活着的对象,用的是串行标记收集的方式,并发收集能够减小对于应用的影响。

老年代设置策略:对于响应时间优先的应用,老年代使用并发收集器,因此其大小须要当心设置,通常要考虑并发会话率会话持续时间等一些参数。若是堆设置小了,可能会形成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;若是堆大了,则须要较长的收集时间。

最优化的方案,通常须要参考如下数据得到:

  • 并发垃圾收集信息
  • 持久代并发收集次数
  • 传统GC信息
  • 花在年轻代和老年代回收上的时间比例
  • 减小年轻代和老年代花费的时间,通常会提升应用的效率

对于吞吐量优先的应用,通常吞吐量优先的应用都有一个很大的年轻代和一个较小的老年代。缘由是,这样能够尽量回收掉大部分短时间对象,减小中期的对象,而老年代尽存放长期存活对象。

较小堆引发的碎片问题 :由于老年代的并发收集器使用标记、清除算法,因此不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样能够分配给较大的对象。可是,当堆空间较小时,运行一段时间之后,就会出现“碎片”,若是并发收集器找不到足够的空间,那么并发收集器将会中止,而后使用传统的标记、清除方式进行回收。若是出现“碎片”,可能须要进行以下配置: -XX:+UseCMSCompactAtFullCollection 使用并发收集器时,开启对老年代的压缩;-XX:CMSFullGCsBeforeCompaction=0上面配置开启的状况下,这里设置多少次Full GC后,对老年代进行压缩。

/*监控实例*/

内存池名称: Tenured Gen

Java 虚拟机最初向操做系统请求的内存量: 3,538,944 字节

Java 虚拟机实际能从操做系统得到的内存量: 1,431,699,456 字节

Java 虚拟机可从操做系统得到的最大内存量: 1,431,699,456 字节。请注意,并不必定能得到该内存量。

Java 虚拟机此时使用的内存量: 1,408,650,472 字节

/*监控实例*/

实例说明:系统能得到的最大Tenured Generation空间大小为1.431G左右,此时使用已经1.408G,基本满了,因此在JVM执行串行标记垃圾收集时,系统响应速度会很慢!

鱼苗养殖区-持久代(Permanent Generation

控制着全部虚拟机本身映射的数据,如类和对象的方法。在持久代中jvm则存储class和method对象。持久代就像鱼苗养殖区同样,池塘主人一次对该区域投入足够量的鱼苗,已保证其余鱼塘的足够供应。就配置而言,永久域是一个独立域而且不认为是堆的一部分。永久域默认大小为4m.运行程序时,jvm会调整永久域的大小以知足须要。每次调整时,jvm会对堆进行一次彻底的垃圾收集。 使用-XX:MaxPerSize标志来增长永久域搭大小。在WebLogic Server应用程序加载较多类时,常常须要增长永久域的最大值。当jvm加载类时,永久域中的对象急剧增长,从而使jvm不断调整永久域大小。为了不调整,可以使用-XX:PerSize标志设置初始值。

1.4     主人按期捕鱼-JVM垃圾回收

一个池塘收容积限制,能养殖的鱼的数量是必定的,所以隔一段时间必须捞出部分长成的鱼来使主人能喂养不少的鱼。一样,JVM所管理的有限内存也要实现最优化利用,Garbage Collection(GC)就是用来释放没有被引用的对象所占领的内存,目的在于清除再也不使用的对象。GC经过算法和参数的配置能够对性能产生效果显著的影响。

GC就好像把长成的鱼从池塘中捞出来拿到市场上去卖,而后给池塘腾出空间继续养别的鱼赚钱。采用哪一种养殖方式能让鱼塘主人赚到更大的利润是鱼塘主人的经营目的,而JVM调优的目的在于如何能是系统表现出更好的响应时间、更大的吞吐量。

Minor Collections(局部垃圾回收):当通用内存消耗完被分配的内存时,JVM会在内存池上执行一个局部的GC(老是调用minor collection)去释放被dead的对象所占用的内存。这个局部的GC一般比彻底GC要快许多。青年代中的垃圾回收就是采用局部垃圾回收机制,所以,青年代中内存分配和管理效率也是最高。

一般状况下,对于内存的申请优先在青年代中申请,当内存不够时会整理新生代,当整理之后仍是不能知足申请的内存,就会向老年代移动一些生命周期较长的对象。这种整理和移动会消耗资源,同时下降系统运行响应能力,所以若是青年代设置的太小,就会频繁的整理和移动,对性能形成影响。那是否把年青代设置的越大越好,其实否则,青年代采用的是复制搜集算法,这种算法必须中止全部应用程序线程,服务器线程切换时间就会成为应用响应的瓶颈。

Major Collections(彻底垃圾回收):当老年代须要被回收,这就是一个major collection ,它的运行经常很是慢,由于它要涉及全部存活着的类。

/*实例*/

垃圾收集器的名称: Copy

使用此垃圾收集器收集的数量: 219 字节

垃圾收集时间: 18 秒 630 毫秒

垃圾收集器的名称: MarkSweepCompact

使用此垃圾收集器收集的数量: 47 字节

垃圾收集时间: 36 秒 166 毫秒

实例说明:copy垃圾搜集器的运行时间为18秒回收219字节,回收速度为平均每秒12字节,而MKC垃圾搜集器的时间为36秒回收了47字节,回收速度为平均每秒1.3字节,二者差距几乎达到了10倍,可见彻底垃圾回收的速度远不如局部垃圾回收。

1.5     不一样的捕鱼方式-垃圾回收器

Sun JVM提供有4垃圾回收器:

Serial Collector(序列垃圾回收器):垃圾回收器对Young Gen和Tenured Gen都是使用单线的垃圾回收方式,对Young Gen,会使用拷贝策略避免内存碎片,对Old Gen,会使用压缩策略避免内存碎片。在JVM启动参数中使用-XX:+UseSerialGC启用Serial Collector。串行收集器只适用于小数据量的状况,默认状况下,JDK5.0之前都是使用串行收集器,若是想使用其余收集器须要在启动时加入相应参数。基本上在多内核的服务器上应该避免使用这种方式。JDK5.0之后,JVM会根据当前系统配置进行判断。串行GC适合小型应用和单处理器系统(无需多线程交互,效率比较高)。

Parallel Collector(并发垃圾回收器):垃圾回收器对Young Gen和Tenured Gen都是使用多线程并行垃圾回收的方式,对Young Gen,会使用拷贝策略避免内存碎片,对Old Gen,会使用压缩策略避免内存碎片。在JVM启动参数中使用-XX:+UseParallelGC启用Parallel Collector。这是一种吞吐量优先的并行收集器 ,主要以到达必定的吞吐量为目标,适用于科学技术和后台处理等。采用了多线程并行管理和回收垃圾对象,提升了回收效率和服务器的吞吐量,适合于多处理器的服务器。

Parallel Compacting Collector(并行压缩垃圾回收器):与Parallel Collector垃圾回收相似,但对Tenured Gen会使用一种更有效的垃圾回收策略,此垃圾回收器在暂停时间上会更短。在JVM启动参数中使用-XX:+UseParallelOldGC启用Parallel Compacting Collector。这是一种响应时间优先的并发收集器 ,主要是保证系统的响应时间,减小垃圾收集时的停顿时间。适用于应用服务器、电信领域等。

Concurrent Mark-Sweep (CMS) Collector(并发标志清除垃圾回收器):对Young Gen会使用与Parallel Collector一样的垃圾回收策略,对Tenured Gen垃圾回收的垃圾标志线程与应用线程同时进行,而垃圾清除则须要暂停应用线程,但暂停时间会大大缩减,须要注意的是,因为垃圾回收过程更加复杂,会下降整体的吞吐量。

这里说一下并行和并发的区别,并行指的是多个进程并行执行垃圾回收,那么能够很好的利用多处理器,而并发指的是应用程序不须要暂停能够和垃圾回收线程并发工做。

说明:对于联机处理的应用系统或复杂的3层应用系统,采用Concurrent Mark-Sweep (CMS) Collector进行垃圾搜集,基本上既能保证性能,又能保证稳定性(暂停时间短)。

1.6     捕鱼工具选择-JVM参数

1.6.1    通用JVM参数

-server

若是不配置该参数,JVM会根据应用服务器硬件配置自动选择不一样模式,server模式启动比较慢,可是运行期速度获得了优化,适合于服务器端运行的JVM。

-client

启动比较快,可是运行期响应没有server模式的优化,适合于我的PC的服务开发和测试。

-Xmx

设置java heap的最大值,默认是机器物理内存的1/4。这个值决定了最多可用的Java堆内存:分配过少就会在应用中须要大量内存做缓存或者临时对象时出现OOM(Out Of Memory)的问题;若是分配过大,那么就会因PermSize太小而引发的另一种Out Of Memory。因此如何配置仍是根据运行过程当中的分析和计算来肯定,若是不能肯定仍是采用默认的配置。

-Xms

设置Java堆初始化时的大小,默认状况是机器物理内存的1/64。这个主要是根据应用启动时消耗的资源决定,分配少了申请起来会下降运行速度,分配多了也浪费。

-XX:PermSize

初始化永久内存区域大小。永久内存区域全称是Permanent Generation space,是指内存的永久保存区域,程序运行期不对PermGen space进行清理,因此若是你的APP会LOAD不少CLASS的话,就极可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。 若是你的WEB APP下用了大量的第三方jar,其大小超过了jvm默认的PermSize大小(4M)那么就会产生此错误信息了。

-XX:MaxPermSize

设置永久内存区域最大大小。

-Xmn

直接设置青年代大小。整个JVM可用内存大小=青年代大小 + 老年代大小 + 持久代大小 。持久代通常固定大小为64m,因此增大年轻代后,将会减少老年代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

按照Sun的官方设置比例,则上面的例子中年轻代的大小应该为2048*3/8=768M。

-XX:NewRatio

控制默认的Young代的大小,例如,设置-XX:NewRatio=3意味着Young代和老年代的比率是1:3。换句话说,Eden和Survivor空间总和是整个堆大小的1/4。

 

如图中的实际设置,-XX:NewRatio=2,-Xmx=2048,则年轻代和老年代的分配比例为1:2,即年轻代的大小为682M,而老年代的大小为1365M。查看实际系统的jvm监控结果为:

内存池名称: Tenured Gen

Java 虚拟机最初向操做系统请求的内存量: 3,538,944 字节

Java 虚拟机实际能从操做系统得到的内存量: 1,431,699,456 字节

Java 虚拟机可从操做系统得到的最大内存量: 1,431,699,456 字节。请注意,并不必定能得到该内存量。

Java 虚拟机此时使用的内存量: 1,408,650,472 字节

即:1,408,650,472 字节=1365M,证实了上面的计算是正确的。

-XX:SurvivorRatio

设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6。越大的survivor空间能够容许短时间对象尽可能在年青代消亡;若是Survivor空间过小,Copying收集将直接将其转移到老年代中,这将加快老年代的空间使用速度,引起频繁的彻底垃圾回收。

以下图:

 

SurvivorRatio的值设为3,Xmn为768M,则每一个Survivor空间的大小为768M/5=153.6M。

-XX:NewSize

为了实现更好的性能,您应该对包含短时间存活对象的池的大小进行设置,以使该池中的对象的存活时间不会超过一个垃圾回收循环。新生成的池的大小由 NewSize 和 MaxNewSize 参数肯定。经过这个选项能够设置Java新对象生产堆内存。在一般状况下这个选项的数值为1024的整数倍而且大于1MB。这个值的取值规则为,通常状况下这个值-XX:NewSize是最大堆内存(maximum heap size)的四分之一。增长这个选项值的大小是为了增大较大数量的短生命周期对象。增长Java新对象生产堆内存至关于增长了处理器的数目。而且能够并行地分配内存,可是请注意内存的垃圾回收倒是不能够并行处理的。做用跟-XX:NewRatio类似, -XX:NewRatio是设置比例而-XX:NewSize是设置精确的数值。

-XX:MaxNewSize

经过这个选项能够设置最大Java新对象生产堆内存。一般状况下这个选项的数值为1 024的整数倍而且大于1MB,其功用与上面的设置新对象生产堆内存-XX:NewSize相同。通常要将NewSize和MaxNewSize设成一致。

-XX:MaxTenuringThreshold

设置垃圾最大年龄。若是设置为0的话,则年轻代对象不通过Survivor区,直接进入老年代。对于老年代比较多的应用,能够提升效率。若是将此值设置为一个较大值,则年轻代对象会在Survivor区进行屡次复制,这样能够增长对象在年轻代的存活时间,增长在年轻代即被回收的几率。

以下图:

 

-XX:MaxTenuringThreshold参数被设置成5,表示对象会在Survivor区进行5次复制后若是尚未被回收才会被复制到老年代。

-XX:GCTimeRatio

设置垃圾回收时间占程序运行时间的百分比。该参数设置为n的话,则垃圾回收时间占程序运行时间百分比的公式为1/(1+n) ,若是n=19表示java能够用5%的时间来作垃圾回收,1/(1+19)=1/20=5%。

-XX:TargetsurvivorRatio

该值是一个百分比,控制容许使用的救助空间的比例,默认值是50。该参数设置较大的话可提升对survivor空间的使用率。当较大的堆栈使用较低的SurvivorRatio时,应增长该值到80至90,以更好利用救助空间。

-Xss

设置每一个线程的堆栈大小,根据应用的线程所需内存大小进行调整,在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右。当这个选项被设置的较大(>2MB)时将会在很大程度上下降系统的性能。所以在设置这个值时应该格外当心,调整后要注意观察系统的性能,不断调整以期达到最优。

JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K。

-Xnoclassgc

这个选项用来取消系统对特定类的垃圾回收。它能够防止当这个类的全部引用丢失以后,这个类仍被引用时不会再一次被从新装载,所以这个选项将增大系统堆内存的空间。禁用类垃圾回收,性能会高一点;

1.6.2    串行收集器参数

-XX:+UseSerialGC:

设置串行收集器 。

1.6.3    并行收集器参数

-XX:+UseParallelGC:

选择垃圾收集器为并行收集器,此配置仅对年轻代有效,即上述配置下,年轻代使用并行收集,而老年代仍旧使用串行收集。采用了多线程并行管理和回收垃圾对象,提升了回收效率,提升了服务器的吞吐量,适合于多处理器的服务器。

-XX:ParallelGCThreads

配置并行收集器的线程数,即:同时多少个线程一块儿进行垃圾回收。此值最好配置与处理器数目相等。

-XX:+UseParallelOldGC:

采用对于老年代并发收集的策略,能够提升收集效率。JDK6.0支持对老年代并行收集。

-XX:MaxGCPauseMillis

设置每次年轻代并行收集最大暂停时间,若是没法知足此时间,JVM会自动调全年轻代大小以知足此值。

-XX:+UseAdaptiveSizePolicy:

设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间或者收集频率等,此值建议使用并行收集器时,一直打开。

1.6.4    并发收集器参数

-XX:+UseConcMarkSweepGC

指定在 老年代 使用 concurrent cmark sweep gc。gc thread 和 app thread 并行 ( 在 init-mark 和 remark 时 pause app thread)。app pause 时间较短 , 适合交互性强的系统 , 如 web server。它能够并发执行收集操做,下降应用中止时间,同时它也是并行处理模式,能够有效地利用多处理器的系统的多进程处理。

-XX:+UseParNewGC

指定在 New Generation 使用 parallel collector, 是 UseParallelGC 的 gc 的升级版本 , 有更好的性能或者优势 , 能够和 CMS gc 一块儿使用

-XX:+UseCMSCompactAtFullCollection:

打开对老年代的压缩。可能会影响性能,可是能够消除碎片,在FULL GC的时候, 压缩内存, CMS是不会移动内存的, 所以, 这个很是容易产生碎片, 致使内存不够用, 所以, 内存的压缩这个时候就会被启用。 增长这个参数是个好习惯。

-XX:+CMSIncrementalMode:

设置为增量模式。适用于单CPU状况

-XX:CMSFullGCsBeforeCompaction

因为并发收集器不对内存空间进行压缩、整理,因此运行一段时间之后会产生“碎片”,使得运行效率下降。此值设置运行多少次GC之后对内存空间进行压缩、整理。

-XX:+CMSClassUnloadingEnabled

使CMS收集持久代的类,而不是fullgc

-XX:+CMSPermGenSweepingEnabled

使CMS收集持久代的类,而不是fullgc。

-XX:-CMSParallelRemarkEnabled

在使用 UseParNewGC 的状况下 , 尽可能减小 mark 的时间。

-XX:CMSInitiatingOccupancyFraction

说明老年代到百分之多少满的时候开始执行对老年代的并发垃圾回收(CMS),这个参数设置有很大技巧,基本上知足公式:

(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn

时就不会出现promotion failed。在个人应用中Xmx是6000,Xmn是500,那么Xmx-Xmn是5500兆,也就是老年代有5500兆,CMSInitiatingOccupancyFraction=90说明老年代到90%满的时候开始执行对老年代的并发垃圾回收(CMS),这时还剩10%的空间是5500*10%=550兆,因此即便Xmn(也就是年轻代共500兆)里全部对象都搬到老年代里,550兆的空间也足够了,因此只要知足上面的公式,就不会出现垃圾回收时的promotion failed;

若是按照Xmx=2048,Xmn=768的比例计算,则CMSInitiatingOccupancyFraction的值不能超过40,不然就容易出现垃圾回收时的promotion failed。

-XX:+UseCMSInitiatingOccupancyOnly

指示只有在老年代在使用了初始化的比例后 concurrent collector 启动收集

-XX:SoftRefLRUPolicyMSPerMB

相对于客户端模式的虚拟机(-client选项),当使用服务器模式的虚拟机时(-server选项),对于软引用(soft reference)的清理力度要稍微差一些。能够经过增大-XX:SoftRefLRUPolicyMSPerMB来下降收集频率。默认值是 1000,也就是说每秒一兆字节。Soft reference在虚拟机中比在客户集中存活的更长一些。其清除频率能够用命令行参数 -XX:SoftRefLRUPolicyMSPerMB=<N> 来控制,这能够指定每兆堆空闲空间的 soft reference 保持存活(一旦它不强可达了)的毫秒数,这意味着每兆堆中的空闲空间中的 soft reference 会(在最后一个强引用被回收以后)存活1秒钟。注意,这是一个近似的值,由于 soft reference 只会在垃圾回收时才会被清除,而垃圾回收并不总在发生。

-XX:LargePageSizeInBytes

内存页的大小, 不可设置过大,会影响Perm的大小。

-XX:+UseFastAccessorMethods

原始类型的快速优化,get,set 方法转成本地代码。

-XX:+DisableExplicitGC 

禁止 java 程序中的 full gc, 如 System.gc() 的调用。 最好加上防止程序在代码里误用了,对性能形成冲击。

-XX:+AggressiveHeap

特别说明下:(我感受对于作java cache应用有帮助)

试图是使用大量的物理内存

长时间大内存使用的优化,能检查计算资源(内存, 处理器数量)

至少须要256MB内存

大量的CPU/内存, (在1.4.1在4CPU的机器上已经显示有提高)

-XX:+AggressiveOpts

加快编译

-XX:+UseBiasedLocking

锁机制的性能改善。

2        实战篇

2.1     测试目的

测试被测系统使用不一样的垃圾回收方案时的性能表现;

了解各类JVM参数在性能调优时的实际效果;

对遴选出的最优方案进行8小时压力测试并记录测试结果;

2.2     测试环境准备

被测程序的运行的软硬件环境:

l  D630 4G内存+T7250双核CPU+160G硬盘;

l  操做系统:windowsXP SP3;

l  IP:11.55.15.51;

被测程序名称:

l  XXX银行采购管理系统V1.1版;

程序部署环境:

l  Tomcat6.0.18  for windows;

l  Sun JDK1.6.13  for windows;

l  Oracle10g  for windows(单独运行在另一台640M笔记本上)

性能测试工具运行的软硬件环境:

l  操做系统:windowsxp sp3

l  浏览器版本:IE7

l  IP地址:11.55.15.141

l  性能测试工具:loadrunner9.10

JVM监控工具:

l  使用Jconsole进行图形化监控;

肯定JVM在被测系统的机器上最大可用内存:

经过在命令行下用 java -XmxXXXXM -version 命令反复测试发如今11.55.15.51机器上JVM能使用的最大内存为1592M。

 

2.3     录制测试脚本

录制前准备:修改checkcode.java文件,将随机生成的校验码改为一个固定的校验码方便脚本的自动运行,而后将编译好的checkcode.class文件替换发布包中的class文件。

选择典型业务操做进行脚本录制,每一个系统的典型业务操做都会不一样,须要通过分析统计,选择用户操做频率最高的部分。通过分析后肯定的脚本内容为:录制系统登陆操做并在登陆成功后的主界面上选取一段文字做为验证点。

启动VuGen程序按脚本定义内容进行录制并调试脚本,保证脚本能正常运行。

2.4     定义测试场景

l  虚拟用户数:30

l  持续运行时间:8小时

l  虚拟用户加载和卸载方式:同时

l  性能监控指标:响应时间、吞吐量、成功交易数

2.5     执行初步性能测试

使用系统默认的参数执行测试,并记录响应时间、吞吐量已经成功交易数等数据,同时监控JVM的使用状况。

2.6     选择调优方案

不一样垃圾回收方法测试数据:

Id

NewRatio

SurviorRatio

TransResponse Time

Throughput

Passed Transactions

1

2

25

3.139s

3016230.514

7528

2

1

25

3.161s

2975581.301

7452

3

3

25

2.814s

3334717.818

8383

4

4

25

2.659s

3505592.450

8846

5

5

25

2.860s

3270596.069

8232

6

4

15

2.499s

3765121.986

9426

7

4

5

1.986s

4750776.581

11843

8

4

4

1.968s

4825608.161

11947

9

4

3

2.507s

3770420.243

9388

10

-XX:TargetSurvivorRatio=90

1.924

4945053.874

12216

11

-Xmx1024M

1.903

4974137.908

12360

并发收集模式,运行时间十分钟后的对内存使用状况:

 

串行收集模式,运行时间十分钟:

 

并行收集模式,运行时间十分钟:

 

30-60:30个并发用户连续运行60分钟的jvm内存变化截图:

 

在11:36和11:56分发生了两次彻底GC(Full GC),由于这时PS Old Gen已经满了,JVM自动对Old Gen中的内存进行了回收。

根据反复的测试并结合被测系统业务特色,最终敲定了使用如下最优方案进行8小时压力测试:

JAVA_OPTS=-server

-Xms1024M

-Xmx1024M

-Xmn128M

-XX:NewSize=128M

-XX:MaxNewSize=128M

-XX:SurvivorRatio=20

-XX:MaxTenuringThreshold=10

-XX:GCTimeRatio=19

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+CMSClassUnloadingEnabled

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=0

-XX:-CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=70

-XX:SoftRefLRUPolicyMSPerMB=0

–XX:PermSize=256m

-XX:MaxPermSize=256m

-Djava.awt.headless=true

2.7     调优后JVM监控图

30Vusers运行8小时截图:

 

 

 

 

 

 

 

2.8     测试结果分析

对于XX银行采购系统的登陆操做来讲,将jvm的NewRatio 和SurviorRatio设置成4时,性能表现最好!在此基础上在设置-XX:TargetSurvivorRatio=90和-Xmx1024M后性能也有必定程度的提高。

3        性能问题举例

3.1     性能症状

XX省一个正式上线运行的系统,每运行一段时间后程序进程会莫名其妙地被kill掉,不得不手工启动系统。

3.2     监控结果

3.2.1    jmap命令查看堆内存分配和使用状况

./jmap -heap 31    //31为程序的进程号

Attaching to process ID 31, please wait...

Debugger attached successfully.

Server compiler detected.

JVM version is 11.0-b12   //显示jvm的版本号

using parallel threads in the new generation.  //说明在年轻代使用了并行收集

using thread-local object allocation.

Concurrent Mark-Sweep GC     //启用CMS收集模式

 

Heap Configuration:

   MinHeapFreeRatio = 40

   MaxHeapFreeRatio = 70   //这两项说明堆内存的使用比例在30%~60%之间

   MaxHeapSize      = 2147483648 (2048.0MB)  //最大堆大小为2048M

   NewSize          = 805306368 (768.0MB)   

   MaxNewSize       = 805306368 (768.0MB)    //年轻代大小为768M

   OldSize          = 1342177280 (1280.0MB)   //老年代代大小为1280M

   NewRatio         = 8                     //这个有点自相矛盾,1:8

   SurvivorRatio    = 3                       //救助区大小占整个年轻代的五分之一

   PermSize         = 268435456 (256.0MB)    //持久代大小为256M

   MaxPermSize      = 268435456 (256.0MB)   //持久代大小为256M

 

Heap Usage:

//年轻代大小,这里只计算了一个救助区,因此少了153M

New Generation (Eden + 1 Survivor Space):  

   capacity = 644284416 (614.4375MB)

   used     = 362446760 (345.65616607666016MB)

   free     = 281837656 (268.78133392333984MB)

   56.25570803810968% used

//Eden Space大小为614.43-153=460.8M

Eden Space:

   capacity = 483262464 (460.875MB)

   used     = 342975440 (327.0868682861328MB)

   free     = 140287024 (133.7881317138672MB)

   70.97084204743864% used

//两个救助区的大小均为153MB, 与前面的SurvivorRatio参数设置值计算结果一致。

From Space:

   capacity = 161021952 (153.5625MB)

   used     = 19471320 (18.569297790527344MB)

   free     = 141550632 (134.99320220947266MB)

   12.092338813530219% used

To Space:

   capacity = 161021952 (153.5625MB)

   used     = 0 (0.0MB)

   free     = 161021952 (153.5625MB)

   0.0% used

//老年代大小为1280M,和根据参数配置计算的结果一致。

concurrent mark-sweep generation:

   capacity = 1342177280 (1280.0MB)

   used     = 763110504 (727.7588882446289MB)

   free     = 579066776 (552.2411117553711MB)

   56.85616314411163% used

//永久代大小为256M,实际使用不到50%。可在系统运行一段时间后稳定该值。

Perm Generation:

   capacity = 268435456 (256.0MB)

   used     = 118994736 (113.48222351074219MB)

   free     = 149440720 (142.5177764892578MB)

   44.32899355888367% used

3.2.2    Top命令监控结果:

 

经过使用top命令进行持续监控发现此时CPU空闲比例为85.7%,剩余物理内存为3619M,虚拟内存8G未使用。持续的监控结果显示进程29003占用系统内存不断在增长,已经快获得最大值。

3.2.3    Jstat命令监控结果:

 

使用jstat命令对PID为29003的进程进行gc回收状况检查,发现因为Old段的内存使用量已经超过了设定的80%的警惕线,致使系统每隔一两秒就进行一次FGC,FGC的次数明显多余YGC的次数,可是每次FGC后old的内存占用比例却没有明显变化—系统尝试进行FGC也不能有效地回收这部分对象所占内存。同时也说明年轻代的参数配置可能有问题,致使大部分对象都不得不放到老年代来进行FGC操做,这个或许跟系统配置的会话失效时间过长有关。

3.2.4    Jstack打印出的堆栈内容:

 

在上图中发现大量的的工做流线程锁定。

 

在上图中发现大量的的cms线程池管理线程锁定。

3.3     缘由分析

经过对jvm内存进行实时监控后发现致使老年代内存不能有效回收的缘由就在于堆栈中存在大量的线程死锁问题。建议开发组认真审查com.zzxy.workflow包的源代码以及com.web.csm包中的源代码,看看是否存在线程死锁的缺陷。

3.4     该系统的JVM设置

<jvm-options>-XX:+PrintGCApplicationConcurrentTime</jvm-options> <jvm-options>-XX:+PrintGCApplicationStoppedTime</jvm-options>

<jvm-options>-XX:+PrintGCTimeStamps</jvm-options>

<jvm-options>-XX:+PrintGCDetails</jvm-options>

<jvm-options>-Xms2048m</jvm-options>

<jvm-options>-Xmx2048m</jvm-options>

<jvm-options>-server</jvm-options>

<jvm-options>-Djava.awt.headless=true</jvm-options>

<jvm-options>-XX:PermSize=256m</jvm-options>

<jvm-options>-XX:MaxPermSize=256m</jvm-options>

<jvm-options>-XX:+DisableExplicitGC</jvm-options>

<jvm-options>-Xmn768M</jvm-options>

<jvm-options>-XX:SurvivorRatio=3</jvm-options>

<jvm-options>-Xss128K</jvm-options>

<jvm-options>-XX:TargetSurvivorRatio=80</jvm-options>

<jvm-options>-XX:MaxTenuringThreshold=5</jvm-options>

<jvm-options>-XX:+UseConcMarkSweepGC</jvm-options>

<jvm-options>-XX:+CMSClassUnloadingEnabled</jvm-options>

<jvm-options>-XX:+UseCMSCompactAtFullCollection</jvm-options>

<jvm-options>-XX:-CMSParallelRemarkEnabled</jvm-options>

4        后记

一、性能调优要作到有的放矢,根据实际业务系统的特色,以必定时间的JVM日志记录为依据,进行有针对性的调整、比较和观察。

二、性能调优是个无止境的过程,要综合权衡调优成本和更换硬件成本的大小,使用最经济的手段达到最好的效果。

三、性能调优不只仅包括JVM的调优,还有服务器硬件配置、操做系统参数、中间件线程池、数据库链接池、数据库自己参数以及具体的数据库表、索引、分区等的调整和优化。

四、经过特定工具检查代码中存在的性能问题并加以修正是一种比较经济快捷的调优方法。

5        附:舍得网的典型配置

$JAVA_ARGS .= " -Dresin.home=$SERVER_ROOT

-server

-Xms6000M

-Xmx6000M

-Xmn500M

-XX:PermSize=500M

-XX:MaxPermSize=500M

-XX:SurvivorRatio=65536

-XX:MaxTenuringThreshold=0

-Xnoclassgc

-XX:+DisableExplicitGC

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=0

-XX:+CMSClassUnloadingEnabled

-XX:-CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=90

-XX:SoftRefLRUPolicyMSPerMB=0

-XX:+PrintClassHistogram

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-XX:+PrintHeapAtGC

-Xloggc:log/gc.log ";
说明:

一、 -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0就是去掉了救助空间;

二、-Xnoclassgc禁用类垃圾回收,性能会高一点;

三、-XX:+DisableExplicitGC禁止System.gc(),省得程序员误调用gc方法影响性能;

四、-XX:+UseParNewGC,对年轻代采用多线程并行回收,这样收得快;

相关文章
相关标签/搜索