JVM 性能调优之内存优化(四)

  • Post author:
  • Post category:其他


今天我们进行JVM 性能优化:

JVM


调优是一个系统而又复杂的过程,但我们知道,在大多数情况下,我们基本不用去调整


JVM


内存分配,因为一些初始化的参数已经可以保证应用 服务正常稳定地工作了。在应用服务的特定场景下,


JVM


内存分配不合理带来的性能表现并不会像内存溢出问题这么突出。一般你没有深入到各项性能指标中去,是很难发现其 中隐藏的性能损耗。


1、压测工具




AB


Ab(ApacheBench)


测试工具是


Apache


提供的一款测试工具,具有简单易上手的特点,在测试


Web


服务时非常实用。

ab


一般都是在


Linux


上用。 安装非常简单,只需要在 Linux


系统中输入


yum-y install httpd-tools


命令,就可以了。

安装成功后,输入


ab


命令,可以看到以下信息:


ab


工具用来测试


post get


接口请求非常便捷,可以通过参数指定请求数、并发数、请求参数等

测试


get


请求接口


ab -c 10 -n 100 http://www.test.api.com/test/login?userName=test&password=test

测试


post


请求接口

ab -n 100 -c 10 -p ‘post.txt’ -T ‘application/x-www-form-urlencoded’ ‘http://test.api.com/test/register’

post.txt


为存放


post


参数的文档,存储格式如

usernanme=test&password=test&sex=1

参数的含义:

-n


:总请求次数(最小默认为


1


);

-c


:并发次数(最小默认为


1


且不能大于总请求次数,例如:


10


个请求,


10


个并发,实际就是


1


人请求


1


次);

-p





post


参数文档路径(


-p





-T


参数要配合使用);

-T





header


头内容类型(此处切记是大写英文字母


T


);

输出中,性能指标参考

Requests per second


:吞吐率,指某个并发用户数下单位时间内处理的请求数;

Time per request


:上面的是用户平均请求等待时间,指处理完成所有请求数所花费的时间


/


(总请求数


/


并发用户数);

Time per request


:下面的是服务器平均请求处理时间,指处理完成所有请求数所花费的时间


/


总请求数;

Percentage of the requests served within a certain time


:每秒请求时间分布情况,指在整个请求中,每个请求的时间长度的分布情况,例如有


50%


的请求

响应在


8ms


内,


66%


的请求响应在


10ms


内,说明有


16%


的请求在


8ms~10ms


之间。


2、JVM




堆内存分配



JVM




内存分配的调优案例


一个高并发系统中的抢购接口,高峰时


5W


的并发请求,且每次请求会产生


20KB


对象(包括订单、用户、优惠券等对象数据)。

我们可以通过一个并发创建一个


1MB


对象的接口来模拟万级并发请求产生大量对象的场景,具体代码如下:


3、AB




压测


对应用服务进行压力测试,模拟不同并发用户数下的服务的响应情况:

1





10


个并发用户


/10


万请求量


(





)

2





100


个并发用户


/10


万请求量


(





)

3





1000


个并发用户


/10


万请求量


(





)

ab -c 10 -n 100000


http://127.0.0.1:8080/jvm/heap

ab -c 100 -n 100000


http://127.0.0.1:8080/jvm/heap

ab -c 1000 -n 100000


http://127.0.0.1:8080/jvm/heap


4、服务器信息


我本机起一台


Linux


虚拟机,分配的内存为


2G


,处理器数量为


2


个。具体信息如下图:


5、GC




监控


还有一句话,无监控不调优,所以我们需要监控起来。


JVM


中我们使用


jstat


命令监控一下


JVM





GC


情况。

统计


GC


的情况。

jstat-gc 8404 5000 20 | awk ‘{print $13,$14,$15,$16,$17}


6、堆空间监控


在默认不配置


JVM


堆内存大小的情况下,


JVM


根据默认值来配置当前内存大小。

我们可以通过以下命令来查看堆内存配置的默认值:

java -XX:+PrintFlagsFinal -version | grep HeapSize


7、调整方案二


java -jar -Xms1500m -Xmx1500m -Xmn1000m -XX:SurvivorRatio=8 jvm-1.0-SNAPSHOT.jar

使用


AB


进行压力测试:

ab -c 10 -n 100000


http://127.0.0.1:8080/jvm/heap



内存优化总结:


一般情况下,高并发业务场景中,需要一个比较大的堆空间,而默认参数情况下,堆空间不会很大。所以我们有必要进行调整。

但是不要单纯的调整堆的总大小,要调整新生代和老年代的比例,以及


Eden


区还有


From


区,还有


To


区的比例。

所以在我们上述的测试中,调整方案二,得到结果是最好的。在三种测试情况下都能够有非常好的性能指标,同时


GC


耗时相对控制也较好。

对于调整方案一,就是单纯的加大堆空间,里面的比例不适合高并发场景,反而导致堆空间变大,没有明显减少


GC


的次数,但是每次


GC


需要检索对象

的堆空间更大,所以


GC


耗时更长。

方案二:调整为一个很大的新生代和一个较小的老年代


.


原因是


,


这样可以尽可能回收掉大部分短期对象


,


减少中期的对象


,


而老年代尽存放长期存活对象。

由于新生代空间较小,


Eden


区很快被填满,就会导致频繁


Minor GC


,因此我们可以通过增大新生代空间来降低


Minor GC


的频率。

单次


Minor GC


时间是由两部分组成:


T1


(扫描新生代)和


T2


(复制存活对象)。


默认情况:



一个对象在


Eden


区的存活时间为


500ms





Minor GC


的时间间隔是


300ms


,因为这个对象存活时间


>


间隔时间,那么正常情况下,


Minor

GC


的时间为 :


T1+T2





方案一:



整堆空间加大,但是新生代没有增大多少,对象在


Eden


区的存活时间为


500ms





Minor GC


的时间可能会扩大到


400ms


,因为这个对象存

活时间


>


间隔时间,那么正常情况下,


Minor GC


的时间为 :


T1*1.5





Eden


区加大了)


+T2


方案二:



当我们增大新生代空间,


Minor GC


的时间间隔可能会扩大到


600ms


,此时一个存活


500ms


的对象就会在


Eden


区中被回收掉,此时就不

存在复制存活对象了,所以再发生


Minor GC


的时间为:即


T1*2


(空间大了)


+T2*0

可见,扩容后,


Minor GC


时增加了


T1


,但省去了


T2


的时间。




JVM


中,复制对象的成本要远高于扫描成本。如果在堆内存中存在较多的长期存活的对象,此时增加年轻代空间,反而会增加


Minor GC


的时间。如

果堆中的短期对象很多,那么扩容新生代,单次


Minor GC


时间不会显著增加。因此,单次


Minor GC


时间更多取决于


GC


后存活对象的数量,而非


Eden

区的大小。

这个就解释了之前的内存调整方案中,方案一为什么性能还差些,但是到了方案二话,性能就有明显的上升。


8、推荐策略


(1)


新生代大小选择

响应时间优先的应用


:


尽可能设大


,


直到接近系统的最低响应时间限制


(


根据实际情况选择


).


在此种情况下


,


新生代收集发生的频率也是最小 的。


同时


,


减少到达老年代的对象


.。


吞吐量优先的应用:


尽可能的设置大


,


可能到达


Gbit


的程度


.


因为对响应时间没有要求


,


垃圾收集可以并行进行


,


一般适合


8CPU


以上的应用。


避免设置过小.


当新生代设置过小时会导致


:1.MinorGC


次数更加频繁


2.


可能导致


MinorGC


对象直接进入老年代


,


如果此时老年代满了


,


会触 发 FullGC.

(2)


老年代大小选择

响应时间优先的应用


:


老年代使用并发收集器


,


所以其大小需要小心设置


,


一般要考虑并发会话率和会话持续时间等一些参数


.


如果堆设置小了


,


可 以会造成内存碎 片,


高回收频率以及应用暂停而使用传统的标记清除方式


; 如果堆大了,


则需要较长的收集时间


.


最优化的方案


,


一般需要参考以下数据获得


: 并发垃圾收集信息、持久代并发收集次数、传统 GC


信息、花在新生代和老年代回收上的时间比例。 吞吐量优先的应用:


一般吞吐量优先的应用都有一个很大的新生代和一个较小的老年代


.


原因是


,


这样可以尽可能回收掉大部分短期对象


,


减少中期的对象


,


而 老年代尽存放长期存活对象。

到此,JVM 性能调优之内存优化分析结束!



版权声明:本文为nandao158原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。