事故背景
今天客戶說風控項目有個別用戶查詢不到數據不是報錯就是一直卡在那里,我就去那個接口看了下。
一看項目日志今天的都幾個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這個對象占據了大部分堆內存。