HotSpot虚拟机将其物理上划分为两个–新生代(young generation)和老年代(old generation)。
新生代(Young generation): 绝大多数最新被建立的对象会被分配到这里,因为大部分对象在建立后会很快变得不可到达,因此不少对象被建立在新生代,而后消失。对象从这个区域消失的过程咱们称之为”minor GC“。html
老年代(Old generation): 对象没有变得不可达,而且重新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。也正因为其相对较大的空间,发生在老年代上的GC要比新生代少得多。对象从老年代中消失的过程,咱们称之为”major GC“(或者”full GC“)java
图中的持久代( permanent generation )也被称为方法区(method area)。他用来保存类常量以及字符串常量。所以,这个区域不是用来永久的存储那些从老年代存活下来的对象。这个区域也可能发生GC。而且发生在这个区域上的GC事件也会被算为major GC。redis
新生代是用来保存那些第一次被建立的对象,他能够被分为三个空间数据库
jstat 是HotSpot JVM提供的一个监控工具api
jstat –gc $<pid$> 1000
缓存
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
服务器
3008.0 3072.0 0.0 1511.1 343360.0 46383.0 699072.0 283690.2 75392.0 41064.3 2540 18.454 4 1.133 19.588
网络
3008.0 3072.0 0.0 1511.1 343360.0 47530.9 699072.0 283690.2 75392.0 41064.3 2540 18.454 4 1.133 19.588
并发
3008.0 3072.0 0.0 1511.1 343360.0 47793.0 699072.0 283690.2 75392.0 41064.3 2540 18.454 4 1.133 19.588
eclipse
这些信息很重要,由于它们展现了GC处理到底花费了多少时间。
在这个例子中,YGC 是217而YGCT 是0.928,这样在简单的计算数据平均数后,你能够知道每次新生代的GC大概须要4ms(0.004秒),而full GC的平均时间为33ms。
可是,只看数据平均数常常没法分析出真正的GC问题。这是主要是由于GC操做时间严重的误差(换句话说,假如两次full GC的时间是 67ms,那么其中的一次full GC可能执行了10ms而另外一个可能执行了57ms。)为了更好地检测每次GC处理时间,最好使用 –verbosegc来替代数据平均数。
为何须要优化GC
或者说的更确切一些,对于基于Java的服务,是否有必要优化GC?应该说,对于全部的基于Java的服务,并不老是须要进行GC优化,但前提是所运行的基于Java的系统,包含了以下参数或行为:
换句话说,若是你没有设定内存的大小,而且系统充斥着大量的超时日志时,你就须要在你的系统中进行GC优化了。
可是,你须要时刻铭记一条:GC优化永远是最后一项任务。
我为GC优化概括了两个目的:
将转移到老年代的对象数量降到最少
按代的GC机制由Oracle JVM提供,不包括能够在JDK7以及更高版本中使用的G1 GC。换句话说,对象被建立在伊甸园空间,然后转化到幸存者空间,最终剩余的对象被送到老年代。某些比较大的对象会在被建立在伊甸园空间后,直接转移到老 年代空间。老年代空间上的GC处理会新生代花费更多的时间。所以,减小被移到老年代对象的数据能够显著地减小Full GC的频率。减小被移到老年代空间的对象的数量,可能被误解为将对象留在新生代。可是,这是不可能的。取而代之,你能够调整新生代空间的大小。
减小Full GC执行时间
Full GC的执行时间比Minor GC要长不少。所以,若是Full GC花费了太多的时间(超过1秒),一些链接的部分可能会发生超时错误。
所以,你须要将老年代空间设定为一个“合适”的值。
影响GC性能的参数
正如咱们在第二篇文章结尾提到的,不要幻想“某我的设定了GC参数后性能获得极大的提升,咱们为何不和他用同样的参数?”,由于不一样的Web服务所建立对象的大小和他们的生命周期都不尽相同。
简单来讲,若是一个任务的执行条件是A,B,C,D和E,一样的任务执行条件换为A和B,你会以为哪一个更快?从通常人的直觉来看,在A和B条件下执行的任务会更快。
Java GC参数也是相同的道理,设定一些参数不但没有提升GC执行速度,反而可能致使他更慢。GC优化的最基本原则是将不一样的GC参数用于2台或者多台服务器,并进行对比,并将那些被证实提升了性能或者减小了GC执行时间的参数应用于服务器。请谨记这一点。
下面这个表格列出了GC参数中与内存大小相关的,能够影响性能的参数。
表1:GC优化须要考虑的Java参数
定义 |
参数 |
描述 |
堆内存空间 |
-Xms |
Heap area size when starting JVM 启动JVM时的堆内存空间。 |
-Xmx |
Maximum heap area size 堆内存最大限制 |
|
新生代空间 |
-XX:NewRatio |
Ratio of New area and Old area 新生代和老年代的占比 |
-XX:NewSize |
New area size 新生代空间 |
|
-XX:SurvivorRatio |
Ratio ofEdenarea and Survivor area 伊甸园空间和幸存者空间的占比 |
我在进行GC优化时常用-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx是必须的。你如何设定NewRatio 会对GC性能产生十分显著的影响。有些人可能会问如何设定Perm区域的大小?你能够经过-XX:PermSize 和-XX:MaxPermSize参数来设定,
当OutOfMemoryError 错误发生而且是因为Perm空间不足致使时,另外一个可能影响GC性能的参数是GC类型。下表列出了全部可选的GC类型(基于JDK6.0)
在分析监控结果后,决定是否进行GC优化
在检查GC状态的过程当中,你应该分析监控结果以便决定是否进行GC优化,若是分析结果代表执行GC的时间只有0.1-0.3秒,那你就不必浪费时间去进行GC优化。可是,若是GC的执行时间是1-3秒,或者超过10秒,GC将势在必行。
可是,若是你已经为Java分配了10GB的内存,而且不能再减小内存大小,你将没法再对GC进行优化。在进行GC优化 以前,你必须想清楚你为何要分配如此大的内存空间。假如当你分1 GB 或 2 GB内存时出现OutOfMemoryError ,你应该执行堆内存转储(heap dump),并消除隐患。
注意:
堆内存转储是一个用来检查Java内存中的对象和数据的文件。该文件能够经过执行JDK中的jmap命令来建立。在建立文件的过程当中,Java程序会暂停,所以不要再系统执行过程当中建立该文件。
若是GC执行时间知足下面全部的条件,就意味着无需进行GC优化了。
上面提到的数字并非绝对的;他们根据服务状态的不一样而有所区别,某些服务可能知足于Full GC每次0.9秒的速度,但另外一些可能不是。所以,针对不一样的服务设定不一样的值以决定是否进行GC优化。
下表展现了内存空间大小,GC执行次数以及GC执行时间三者间的关系。
关于如何设置内存空间的大小,没有惟一的标准答案。若是服务 器资源足够,并且Full GC也可能在1秒内完成,设置为10GB固然可行。。但绝大多数服务器并非这样,当内存设为10GB时,可能要花费10~30秒来执行Full GC。固然,执行时间会随对象的大小而改变。
鉴于如此,咱们应该如何设定内存空间大小呢?一 般来讲,我建议为500MB。不过请注意这不是让你将WAS的内存参数设置为–Xms500m 和–Xmx500m。根据优化GC以前的状态,若是 Full GC执行以后内存空间剩余300MB,那么最好将内存设置为1GB(300MB(默认程序占用)+ 500MB(老年代最小空间)+200MB(空闲内存))。也就是说你要为老年代额外设置500MB。所以,若是你有三个执行服务器,内存分别设置为 1GB,1.5GB,2GB,而且检查结果。
理论上来说,GC执行速度应该遵循1GB> 1.5GB> 2GB,所以1GB执行GC速度最快。可是并不说明1GB空间的Full GC会花费1秒而2GB空间会花费2秒。时间取决于服务器的性能和对象的大小。所以,最佳的方式是创建尽量多的衡量指标来监控他们。
对于内存空间大小,你应该额外设定NewRatio参数。 NewRatio参数是新生代和老年代空间的比例,即XX:NewRatio=1意味着新生代与老年代之比为1:1。对于1GB来讲就是新生代和老年代各 500MB。若是NewRatio为2,意味着新生代老年代之比为1:2,所以该值越大,老年代空间越大,新生代空间越小。
这看似一件不是很重要的事情,但NewRatio参数会显著地影响整个GC的性能。若是新生代空间很小,会用更多的对象被转移到老年代空间,这样致使频繁的Full GC,增长暂停时间。
你能够简单的认为NewRatio 为1是最佳的选择,可是,有时可能设置为2或3更好,我就见过不少这样的例子。
如何最快的完成GC优化?对比性能测试的结果 应该是最快地方法,为每一台服务器设置不一样的参数并监控他们的状态,强烈建议至少监控1或2天的数据。可是,当你对GC优化是,你要确保每次执行相同的负 载。而且请求的比率,例如URL都应该是一致的。不过,即使对于专业测试人员要想精确的控制负载也是很难的,并要花费大量的时间准备。所以,相对来讲比较 方便和容易的方法是调整才参数,以后花费较长的时间收集结果。
示例1
下面这个例子针对 Service S的优化,对于最近被部署的 Service S,Full GC花费了太长的时间。
请看 jstat –gcutil的执行结果。
1 2 |
|
最左边的Perm 空间对于最初的GC优化不是很重要,这一次YGC参数的值更加有用。
Minor GC和Full GC的平均值以下表所示
表3:Service S的Minor GC 和Full GC的平均执行时间
GC 类型 |
GC 执行次数 |
GC 执行时间 |
平均 |
Minor GC |
54 |
2.047 |
37 ms |
Full GC |
5 |
6.946 |
1,389 s |
最重要的是下面两个数据
所以,总的内存空间为2GB,不算Perm空间的话,新生代与老年代之比为1:9。经过jstat和-verbosegc 日志进行数据收集,并把三台服务器按照以下方式设置。
一天以后,检查系统的GC日志后发现,在设置了NewRatio参数后很幸运的没有发生Full GC,
为何?
咱们看到NewRatio=4 是最佳的参数,虽然它的新生代空间最小,但GC时间确最短。设定这个参数以后,系统没有执行过Full GC。
为了说明这个问题,下面是服务之星一段时间后执行jstat –gcutil的结果
1 2 |
|
你可能会认为由于服务器接受的请求少才致使的GC执行频率降低。实际上,虽然Full GC没有执行,可是Minor GC被执行了 2,424次。
示例2
这是一个针对ServiceA的例子,咱们经过公司内部的应用性能管理系统(APM)发现JVM暂停了至关长的时间(超过8秒),所以咱们进行了GC优化。咱们找到了Full GC执行时间过长的缘由,并着手解决。
进行GC优化的第一步,就是咱们添加了-verbosegc参数,并获得以下结果。
图1:进行GC优化以前的STW时间
如上图所示,由HPJMeter自动生成的图片之一。X坐标表示JVM执行的时间。Y坐标表示每次GC的时间。CMS绿点,表示Full GC结果。Parallel Scavenge蓝点,表示Minor GC结果。
以前我曾经说过CMS GC是最快的,可是上面的的结果显示出于某种缘由,它最多花费了15秒。是什么致使这个结果?是否想起我以前提过的,CMS在进行内存清理时,会变慢。与此同时,服务的内存被设定为 –Xms1g和–Xmx4g ,且实际分配了4GB内存。
所以,我将GC类型从CMS改成Parallel GC。而且将内存改成2GB,设定NewRatio 为3。几小时以后我使用 jstat –gcutil获得以下结果
1 2 |
|
相对于4GB时的15秒,Full GC变成了平均每次3秒。可是3秒同样比较慢,所以我设计了以下6种场景。
那一个最快呢?结果显示,内存越小,结果越好。下图展现了Case6的结果。这是GC的性能最好。最长的响应时间只有1.7秒。平均时间在1秒以内。
图2:Case6的时间图表
基于以上结果。咱们按照Case6调整了GC参数。可是,这致使了天天晚上都会发生OutOfMemoryError。在这里很难解释具体的缘由。简单来讲,批处理程序致使了内存泄漏。相关的问题已经被解决。
若是对GC日志只分析很短的时间就贸然对全部服务器进行优化是很是危险的。请时刻牢记,你必须同时分析GC日志和应用程序。
咱们回顾了两个关于GC优化的例子,正如我以前提到的,例子中提到的GC参数,能够设置在相同的服务器之上,但前提是他们具备相同的CPU,操做系统,JDK版本以及运行着相同的服务。可是不要直接把我用过的参数用到你的服务至上,它们未必能很好的工做。
若是测试的结果知足了预期,那么你不须要对程序进行性能调优。若是没有达到预期结果,你就应该执行调优来解决问题。接下来会经过实例讲解方法。
stop-the-world耗时过长多是因为GC参数不合理或者代码实现不正确。你能够经过分析工具或堆内存转储文件(Heap dump)来定位问题,好比检查堆内存中对象的类型和数量。若是在其中找到了不少没必要要的对象,那么最好去改进代码。若是没有发现建立对象的过程当中有特别 的问题,那么最好单纯地修改GC参数。
为了适当地调整GC参数,你须要获取一段足够长时间的GC日志,还必须知道哪些状况会致使长时间的stop-the-world。想了解更多关于如何选择合适的GC参数,能够阅读我同事的一篇博文:How to Monitor Java Garbage Collection。
当系统发生阻塞,吞吐量和CPU使用率都会下降。这多是因为网络系统或者并发的问题。为了解决这个问题,你能够分析线程转储信息(Thread dump)或者使用分析工具。阅读这篇文章能够得到更多关于线程转储分析的知识:How to Analyze Java Thread Dumps。
你可使用商业的分析工具对线程锁进行精确的分析,不过大部分时候,只需使用JVisualVM中的CPU分析器,就能得到足够的信息。
若是吞吐量很低可是CPU使用率却很高,极可能是低效率代码致使的。这种状况下,你应该使用分析工具定位代码中性能的瓶颈。可以使用的工具备:JVisualVM、Eclipse TPTP或者JProbe。
建议你使用以下方法对程序进行调优。
首先,检查性能调优是否必要。测量性能不是一件简单的工做,你也不能保证每次都得到满意的结果。所以若是程序已经知足预期性能需求,没必要在调优上增长额外的投入了。
问题只出在一个地方,你要作的就是去解决掉它。二八定律(Pareto principle)对性能调优一样适用。这不是说某个模块的低性能必定只源于一个问题,而是强调咱们应该在调优时把注意力放在影响最大的那个问题上。在处理好了最重要的以后,你才应该去解决剩下其余的。也就是建议一次只对一个问题进行修复。
另外须要考虑到气球效应(Balloon effect),有得必有失。你能够经过使用缓存来提升响应能力,可是当缓存逐渐增大,执行一次Full GC的时间也会更长。通常而言,若是你但愿内存使用率比较低,那么吞吐量和响应能力可能都会恶化。所以,要知道什么对本身程序来讲最重要的,而哪些又是次要的。
到此为止,你应该已经了解了如何对Java程序进行性能调优。为了介绍性能测定的具体过程,我不得不省略其中一些细节,不过我认为这些也足够应对大多数Java Web服务端程序了
1. 一般拿到一个性能需求,须要了解接口模型,整个接口的使用比例和容量,系统结构和技术方案,数据库缓存和索引分布,设计出合理的测试计划,设计的原则是尽量真实的模拟线上状况。
2.根据设计出的测试计划,使用一个线程运行测试计划,得出测试结果,对比测试指标
3.以线性增加的方式增长并发数,比较测试结果是否为线性增加
4.测试结果平缓区一般表明着系统容量的最大区域,分析此时的测试结果,如 TPS和响应时间等
5.分析数据库TPS、QPS等数据,redis命中率,MQ吞吐率,javaGC,程序占用时间等各方面因素提升系统性能
6.经过jstack分析各模块的资源占用状况。
7.优化后的程序要通过大批量数据测试确保测试程序不会发生错误
8.根据测试概要报告,数据库监控数据等数据整合测试报告
9. 须要关注api层用的是长链接仍是短链接,长链接的话须要在jmeter里勾选keep alive
1.使用命令行启动,减小界面形成的性能问题
2.命令行记录聚合报告,不要启动过多的其余监控报告
3.尽量关闭没必要要的日志,包括测试代码的log,jmeter自身日志。
4.确保测试脚本计时准确,需确认好那些步骤须要计时,那些在准备在计时以外
5.过多的并发使用分布式请求,当本地cpu,mem占用比较大时,修改jmeter.properties,增长远程remote-server,并启动(具体操做见jmeter操做手册)
1.根据聚合报告分析90%,95%,和最大等指标,找出程序广泛的响应时间。
2.让开发人员查询最大响应时间时的日志,分析程序层面的执行任务状况
3.使用jstack等分析当前程序的资源等待状况,系统资源状况
值得关注的线程状态有:
死锁,Deadlock(重点关注)
执行中,Runnable
等待资源,Waiting on condition(重点关注)
等待获取监视器,Waiting on monitor entry(重点关注)
暂停,Suspended
对象等待中,Object.wait() 或 TIMED_WAITING
阻塞,Blocked(重点关注)
中止,Parked
示例图
4.分析gc结果,cpu,IO等状况,分析程序是否充分利用了系统资源
5.对比分析各并发数的程序响应状况。
分类: 性能测试