GC是会消耗系统资源的,主要是CPU资源,一般情况下,GC消耗的CPU秒数不应该高于全部CPU资源的1/10,正常情况下可能只有百分之一。

前些天面试有人问到jvm调优,说这样一个场景,假设有个java web程序,请求量很大,每次请求即执行了一个我们实现的方法,这个方法里面申请的内存都在eden区进行,如果eden区太小,那么minorGC的频率就比较高,就会占比较多系统资源,从而导致请求QPS下降。所以jvm调优就可以加大eden区:old区的比例。

那么问题来了,eden区多大会对性能产生比较明显的影响呢?

来做一个实验,使用Spring MVC,提供一个url,这个url可以指定这个url里面申请一个指定大小的数组(这个数组会在eden区分配,如果eden区分不下,就会去old区分配)。在本机压测。代码:https://code.pugwoo.com/learning/java-labs/src/branch/master/jvm-qps-benchmark

堆内存256M,eden区86M,老年区175M

这个简单的web项目起来后不请求,它也会每隔7分钟来一次fullGC。

每次调用申请内存 并发数 稳定qps GC情况
1Byte 1 5000 没有fullGC,都是minorGC,频率大概是0.5秒一次,回收时间1.4毫秒
1024Byte 1 7100 没有fullGC,频率和1B一样
1M 1 3800 没有fullGC,频率是20毫秒一次minor gc,回收时间1.4毫秒(回收时间不变)
10M 1 800 fullGC了一次,miorGC的频率是10毫秒一次,每次1.4毫秒;此时数据已经有在old区分配了
50M 1 173 fullGC了14次,共花了0.5秒;minorGC是5毫秒一次,每次1.7毫秒
50M 5 48 开始报OOM了,但服务还正常;验证了接口不受影响的,进程数也没少

总结:

  1. 当每次请求占10M内存(增大10倍)时,qps已经是1M的20%。可以认为这个是分配内存的性能损耗,GC的时间占比从7%增长到14%,按计算GC因素会使得qps降低10%左右。所以可以认为,此时GC还不是性能下降的主要原因。

  2. 当每次请求占50M内存(增大5倍)时,qps已经是10M内存的22%。GC时间从14%增长到40%左右,按计算GC因素会使得qps降低40%。所以此时GC已经是性能瓶颈了。

一般来说,GC时间占比超过10%就不正常了,正常情况下GC时间占比只有1%到3%这样。

堆内存1024M,eden区348M,老年区676M

每次调用申请内存 并发数 稳定qps GC情况
1M 1 3893 minorGC的频率是60毫秒一次,每次1毫秒(看样子虽然eden大了4倍,但是回收时间并不增加,反而还少了)
10M 1 965 没有fullGC,minorGC频率35毫秒,每次1.6毫秒
50M 1 256 fullGC了1次,花了140毫秒;minorGC频率是22毫秒,每次1.8毫秒

总结:

  1. 内存加大4倍,对于1M的内存申请,但线程的qps并没有明显增加(增加了2.5%),此时GC的时间占比从7%降低到1.7%。

  2. 对于10M的内存申请,qps增加了21%,GC时间占比从14%降低到4.5%。

  3. 对于50M的内存申请,qps增加了48%,GC时间占比从40%降低到10%左右。

结论

当GC的资源占用是瓶颈时(超过10%,甚至达到50%),就会表现出qps的显著差异。

文档更新时间: 2020-08-21 22:32   作者:nick