天天看點

記一次線上環境的記憶體溢出(java.lang.OutOfMemoryError)

事故背景

今天客戶說風控項目有個别使用者查詢不到資料不是報錯就是一直卡在那裡,我就去那個接口看了下。

一看項目日志今天的都幾個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      
記一次線上環境的記憶體溢出(java.lang.OutOfMemoryError)

就是這個loanInfos對象,于是猜想應該是如下這個方法出問題了。

記一次線上環境的記憶體溢出(java.lang.OutOfMemoryError)

再進去看doNoFindUserReq()這個方法。。。

記一次線上環境的記憶體溢出(java.lang.OutOfMemoryError)

注意了解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,控制台也會列印資訊。

記一次線上環境的記憶體溢出(java.lang.OutOfMemoryError)

這樣很容易看出哪裡出問題了。

除了上面加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

記一次線上環境的記憶體溢出(java.lang.OutOfMemoryError)

很容易看出是OOMObject這個對象占據了大部分堆記憶體。