起因
今日收到一个同事的求救信息,说正在做gRPC接口测试,用的是jmeter的一个第三方插件,叫jmeter-grpc-request,平日用着挺好用的,今天设置了100个线程,持续跑,结果才跑了5000来个请求,就卡住了。
卡住了?什么是卡住了呢?
我仔细问了,才知道是jmeter整个没有响应了,只能强行杀进程才能停止。这是怎么回事呢?
场景重现
我问同事要了jmeter的脚本文件,并且下载了这个gRPC取样器的插件,在我本机试了一下,果然,线程数量很少时候,运行正常,但是数量多了一些(仅仅到了50),很快就出现了jmeter无响应的情况。
根据经验,立刻切到启动jmeter的命令行界面,看到的提示是内存溢出(OutOfMemoryException)。
这是很奇怪的现象,按理来说,仅仅是50个线程,才跑了几千的请求量,怎么就内存溢出了呢?我们知道,jmeter默认的运行内存是1G(HEAP=”-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m”)几千的请求量,什么东西占用了1G的Heap空间?
内存分析
jmeter工具还是挺方便的,在出现了内存溢出后,自动dump出了此时的JVM情况,在当前的运行目录下生成了java_pid.hprof文件(id是当时jmeter的进程ID)。
所以我用jhat命令来读取hprof文件,看看到底什么东西占用了这么多内存:
$ jhat -port 7001 java_pid<id>.hprof
这里我用了-port参数指定了7001端口,因为默认的7000端口已经被我机器上别的程序占用了。
经过一段时间的等待(dump出来的hprof文件文件太大了,上G),命令行提示已读取完成,此时jhat会启动一个Web服务器,打开浏览器输入http://localhost:7001就可以看到jvm中加载的所有对象。
当然,我要看的是Heap中这些对象的占用空间情况,所以通过最底下的链接,切换到了heap内存分析页:
很明显,排在前几行的就是“罪魁祸首”了。
也许有的小伙伴不清楚,class [B是什么,其实就是java中的byte数组,居然占了300多MB的内存空间。
其次,就是FiledDescriptor对象,占了200多MB的内存空间。这两个大户,耗费了一半的Heap内存,难怪内存不够了。
那么现在的问题就在于两个地方,
这个byte数组,存了什么,为何这么多;
这个FiledDescriptor为何这么多实例。
这就只能从代码角度来分析了。
代码分析
根据对jmeter组件开发的了解,代码从继承AbstractSampler的Sampler开始。当在jmeter中开始运行取样器时,执行的就是sample方法,仔细看看sampler中的sample方法:
@Override
public SampleResult sample(Entry ignored) {
GrpcResponse grpcResponse = new GrpcResponse();
SampleResult sampleResult = new SampleResult();
try {
initGrpcClient();
sampleResult.setSampleLabel(getName());
String grpcRequest = clientCaller.buildRequestAndMetadata(getRequestJson(),getMetadata());
sampleResult.setSamplerData(grpcRequest);
sampleResult.setRequestHeaders(clientCaller.getMetadataString());
sampleResult.sampleStart();
grpcResponse = clientCaller.call(getDeadline());
sampleResult.sampleEnd();
sampleResult.setSuccessful(true);
sampleResult.setResponseData(grpcResponse.getGrpcMessageString().getBytes(StandardCharsets.UTF_8));
sampleResult.setResponseMessage