首页>>后端>>java->【参考】JVM吞吐量和内存占用优化

【参考】JVM吞吐量和内存占用优化

时间:2023-11-30 本站 点击:1

简单介绍,ydata-server该应用主要的功能是,接收上游kafka的数据,然后进行序列化、归类,再发送到kafka中。部署基于Java8的版本。

优化前

内存的占用

4G的容器内存,压测到了3.7G上下,已经达到了90%以上的使用率。

垃圾回收情况

压测1个多小时,发生了4次的Full GC。吞吐量看着还好。。

各项配置堆内存参数:

JVM虚拟机,除了堆内存之外,还有虚拟机栈、方法区、直接内存、容器内存等。所以将容器的所有内存全部分配给堆空间,可能会导致容器内存使用率超出限制。

也确实监测到了超出容器内存限制的情况。。。

第一次优化

优化思路

ydata-server的所有功能,只是简单的消费数据,序列化等等,无使用直接内存的逻辑。

可以这么理解,ydata-server所有的功能,都在堆内存上进行。

而堆空间的分配,新生代占比为1/3,老年代占比为2/3,但是在观测垃圾回收的数据,老年代并没有发生很多次的垃圾回收,而且占用内存很少。

很显然,高频使用的新生代内存,仅占堆内存的1/3,而占2/3的老年代内存,却长期闲置,造成内存使用率过高。

所以,可以适当提高新生代内存的比例。通过-XX:NewSize固定新生代内存的大小。

在观测垃圾回收的数据时,还发现了ydata-server发生了4次的Full GC,虽然4次 Full GC,任然是一个很小的数值,但是发生的原因,值得深入分析。

ydata-server主要的逻辑,是从kafka接收数据,并序列化等等,这些操作很快,而且操作完,相关的对象就应该被回收。理论上,ydata-server应该仅仅会引起新生代的垃圾回收,不会引起老年代的垃圾回收(Parallel 的垃圾回收器,老年代进行并行化的Full GC)。

那是什么原因引起了老年代的垃圾回收呢?

直接原因是,老年代的对象发生了堆积。(废话)

那是什么原因,造成了老年代的对象发生了堆积呢?

下图是对象的一般分配过程,可以看到,当S区的内存不足以放下Eden区存活的对象时,会直接放进老年代

再看一下之前的垃圾回收情况,S区的大小仅为3M,也就是说,如果有一批3M左右的数据在Eden区存活时,会直接进入老年代,从而造成老年代的堆积。

ydata-server还提供一定的rest接口,而如果接口返回的数据在3M左右的话,那么这些数据可能会直接进入老年代。

对前端页面的接口进行查看,发现确实有一些接口,返回的数据比较大。

对其重新发送请求,并对堆内存进行实时监控,最终发现,老年代内存轻微上涨。 积少成多,多次的页面查询,造成老年代的堆积,但总体上页面查询不频繁(还处于测试阶段),所以垃圾回收也仅有几次。

为此,可以加大堆内存中S区的大小,使其能够充分发挥作用。S区的大小比例,为新生代的2/10,但是Parallel会自动调整S区的大小,以达到最大的系统吞吐量,调着调着,S区就越来越小了。。。可以通过禁用自动调整(-XX:-UseAdaptiveSizePolicy)或者使用CMS GC解决。

优化参数

-Xmx2g-Xms2g-XX:NewSize=1g-XX:-UseAdaptiveSizePolicy

内存占用

内存占用稳定在了1.4G上下,较之前下降2G。内存占用率为35%。

垃圾回收情况

// 现场的截图已经找不到了,只能文字描述

仍然发生了3次的Full GC ,但这3次的Full GC,是在压测前就已经发生了。在压测的过程中,再也没有发送过Full GC。

新生代垃圾回收频繁,基本上每秒就会有一次Young GC,每次耗时在130毫秒上下,也就是说,JVM垃圾回收的吞吐量在87%上下,并不是十分优秀。

新生代垃圾回收频繁,没办法了,数据量过来就这么多,所以,只能够增大新生代的内存大小,而老年代,内存稳定,不用再进行增加。

第二次优化

优化思路

启动后垃圾回收的情况:

可以看到,进行了3次的Full GC ,但是老年代的占用很小。所以,应该是其他地方触发了Full GC。

除了堆内存之外,当方法区不够,也会触发Full GC 。而在刚启动时,方法区内存太小,会进行扩容,在扩容时,就发生了Full GC。

所以,启动时的3次Full GC,可能是元空间水位线上涨引起的。

可以通过指定元空间的大小,进行规避,如-XX:MetaspaceSize=256m,Java8之前为设置永久代相关的参数。

由于-Xmx2g内存太小,导致频繁的 Young GC,所以,适当调大内存,使JVM吞吐量达到95%上下。当然如果内存大小的优先级比较高,也可以适当牺牲吞吐量,来换取更小的内存占用。

Parallel Old GC在老年代满时,势必会引起Full GC,为了避免一些考虑不到位的情况导致的Full GC,所以,应该尽量让老年代的内存维持在一个较低的值。可以使用 CMS GC,通过他的Majar GC的机制,让老年代的内存占用保持在一个较低的值。

优化参数

-Xmx3g-Xms3g-XX:NewSize=2g-XX:+UseConcMarkSweepGC-XX:MetaspaceSize=256m

内存占用

内存占用较之前-Xmx2g的设置,略有上升,但仍然保持在了60%上下,且非常稳定。

垃圾回收

经过长时间的压测,在也没有看到Full GC,并且新生代的垃圾回收次数和耗时都明显减少,JVM吞吐量已经达到了95%以上。

总结

1、需要明确调优的目的,如吞吐量,内存占用

2、调优的前提是监控,在此次调优的过程中,使用到了java自带的jstat命令,k8s中部署的普罗米修斯。并且对内存、CPU、网络流量(预估从Kafka接收到的数据规模)、JVM垃圾回收等进行监控。需要熟练使用相关的监控工具。

3、需要了解各Java版本默认的垃圾回收器,及其原理和特点,了解对应GC的相关参数

4、需要对业务功能和代码有一定的理解阅读能力,如果都不知道这个服务是干什么的,那根本无从上手。必要时要阅读项目源码,了解其实现的相关细节。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/4914.html