一次线上OOM问题排查

一、短信预警
某天下午风和日丽,下午五点钟早早的就发版上线。七点准备下班的时候突然收到短信预警,项目OOM了,wtf !!!

二、问题排查
1、出现OOM问题了,脑袋里第一反应就是项目中出现内存泄露或者内存溢出了。先登录ELK,根据关键词 “java.lang.OutOfMemoryError” 进行搜索,果然发现有OOM错误日志。

org.springframework.scheduling.quartz.JobMethodInvocationFailedException: Invocation of method 'run' on target class [class com.kingdee.finance.data.credit.ydreport.task.YdReportAppendixInfoTask] failed; nested exception is java.lang.OutOfMemoryError: Java heap space
    at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:266)
    at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:75)
    at org.quartz.core.JobRunShell.run(JobRunShell.java:213)
    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:557)
Caused by: java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:622)
    at java.lang.StringBuffer.append(StringBuffer.java:383)
    at java.net.URLEncoder.encode(URLEncoder.java:271)
    at com.longsec.http.HttpConnection.httpPostWithJSON(HttpConnection.java:548)
    at com.kingdee.finance.data.credit.ydreport.service.impl.YdCreditAppendixInfoHandler.sendRequest(YdCreditAppendixInfoHandler.java:151)
    at com.kingdee.finance.data.credit.ydreport.service.impl.YdCreditAppendixInfoHandler.handleAppendixRequest(YdCreditAppendixInfoHandler.java:46)
    at com.kingdee.finance.data.credit.ydreport.service.impl.YdCreditAppendixInfoHandler$$FastClassBySpringCGLIB$$a4a2cc16.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:671)
    at com.kingdee.finance.data.credit.ydreport.service.impl.YdCreditAppendixInfoHandler$$EnhancerBySpringCGLIB$$e0cc861a.handleAppendixRequest(<generated>)
    at com.kingdee.finance.data.credit.ydreport.task.YdReportAppendixInfoTask.handleAppendixInfoTask(YdReportAppendixInfoTask.java:56)
    at com.kingdee.finance.data.credit.ydreport.task.YdReportAppendixInfoTask.run(YdReportAppendixInfoTask.java:43)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:265)
    at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:257)
    ... 3 more

2、根据OOM堆栈错误日志,去扫了一眼代码并未看出什么问题。既然代码没有明显的内存泄露迹象,只能把目光转向堆内存对象分析了。

3、从服务器上导出堆内存溢出的dump文件,用mat 打开分析一波。
mat打开dump文件
可以看到一个StringBuffer占了400M,另一个String对象占了224M,堆内存溢出跟这两个对象脱不了干系。搜索了下代码发现项目中出现问题的地方并没有用到StringBuffer这个类,应该是某个jar里有引用到的。点击String对象,看看value。
查看String的Value

4、根据String 对象的值,推测这是一个json格式的参数。用“username”和“businessNumber” 在代码中搜索。
代码1
这段代码就是组装请求参数,上面224M的String对象就是由这段代码产生。但出现400M的StringBuffer对象才是导致OOM的元凶,需要继续追查。
代码2
代码3
代码4
代码5
整理出代码的调用关系,packRequestJson —> sendRequest —> HttpConnection.httpPostWithJSON —> URLEncoder.encode。很显然StringBuffer对象是由jar包中的URLEncoder.encode 方法中产生,这也跟日志中的堆栈日志正好也能对应上。
日志

三、问题原因
根据上面分析以及查看代码,得知定时任务一次拉取 300条记录批次处理,组装参数得到一个224M 大小的String对象,然后在HTTP请求发送之前经过 URLEncoder.encode 方法 产生了一个400M大小的StringBuffer,直接导致了“java.lang.OutOfMemoryError” 异常。

四、解决方案
400M的大对象是由于一次取出300条记录,参数拼接生成的。可以将批处理记录数调小一点,就可以解决问题,批处理记录数应从数据配置中获取。

四、总结
1、线上异常预警,首先要查看项目日志。根据错误日志,来判断问题出现原因。确定好问题排查方向再选择相应的工具进行分析,多角度的观察,抽丝剥茧,直到找到问题的根源。

2、问题排查要熟悉Java应用排查问题常用工具,比如:jstack、jmap、jps。
查看某个应用的进程id以及启动参数:jps -v | grep “xxx”
dump堆内存:jmap -dump:live,format=b,file=/heapdump.hprof {pid}
导出线程栈:jstack {pid} > /jstack.txt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值