事故背景
今天客户说风控项目有个别用户查询不到数据不是报错就是一直卡在那里,我就去那个接口看了下。
一看项目日志今天的都几个g了,平常也就几百兆吧,很明显出了问题。
请求接口后使用命令tail -f 实时查看日志,发现有个东西一个在刷屏,几分钟了还在刷。
把日志切割后查看还发现了堆内存溢出错误,使用命令 free -m 发现服务器4g内存几乎已经占满了。
[2018-07-12 14:06:46,259 ERROR]:[http-bio-443-exec-12] - 错误提示 :org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: Java heap space at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:978) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861) at javax.servlet.http.HttpServlet.service(HttpServlet.java:624) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:731) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.OutOfMemoryError: Java heap space
就是这个loanInfos对象,于是猜想应该是如下这个方法出问题了。
再进去看doNoFindUserReq()这个方法。。。
注意理解noLoanInfosMapper.select()方法,用过通用mapper插件的应该知道这是根据实体类里的属性进行查询。
但是这里就出现了一个问题,如果record.getTrxNo()是null的话(注意红框标注的部分之前是没有的)就相当于没有条件了,就会查询全表的数据。我看了下数据库,对应的表数据大概有17万行,也就是创建了17W个NoLoanInfos对象,所以堆内存都被用光了。
而且后面还有针对这个list的for循环操作。。。
解决办法就是添加了一个判断条件。
总结一下,导致java.lang.OutOfMemoryError内存溢出的几个原因:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小;
更多可以参考这里:http://outofmemory.cn/c/java-outOfMemoryError
其实,我这里写的很简单,但当时没处理过这种情况也是折腾了半天,还要被客户不停地催,不过好在下次就有经验了。
补充,可以通过更专业的方法来分析。
生成dump文件
首先,添加jvm参数生成dump文件和打印堆栈信息。
package cn.sp.chapter2; import java.util.ArrayList; import java.util.List; /** * @Author: 2YSP * @Description: java堆内存溢出异常测试 * @Date: Created in 2018/1/15 */ public class HeapOOM { static class OOMObject{ } /** * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:file * @param args */ public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while (true){ list.add(new OOMObject()); } } }
运行后对应文件夹生成dump文件java_pid11708.hprof,控制台也会打印信息。
这样很容易看出哪里出问题了。
除了上面加jvm参数的方式,还可以通过命令手动生成堆文件。
jcmd process_id GC.heap_dump /path/to/heap_dump.hprof
或者
jmap -dump:live,file=/path/to/heap_dump.hprof process_id
分析dump文件的工具
1.IBM Memory Analyzer
2.Eclipse Memory Analysis
很容易看出是OOMObject这个对象占据了大部分堆内存。