線上問題處理乾貨(系列二)--如何分析docker Java項目內存泄露和溢出等問題?

01、線上JVM調優

1.主要參數

#JVM x參數
#非標準化參數
-Xint: 解釋執行
-Xcomp:第一次使用就編譯成本地代碼
-Xmixed:混合模式,JVM自己來決定是否編譯成本代碼
#XX參數分類
格式:-XX:[+-]<name>表示啓用或者禁用name屬性比如:  -XX:+UseConcMarkSweepGC  -XX:UseG1GC  非Boolean類型格式:-XX:<name> = <value>表示name屬性的值是value比如:  -XX:MaxGCPauseMillis=500  XX:GCTimeRatio=19  不是X參數,而是XX參數
-Xms等價於-XX:InitialHeapSize-Xmx等價於-XX:MaxHeapSize
查看JVM運行時參數
-XX:+PrintFlagsInitial-XX:+PrintFlagsFinal-XX:+UnlockExperimentalIVMOptions解鎖實驗參數

2.怎麼查看項目呢?

    這裏主要講Java項目,其它項目不在考慮範圍之內。

jps --查看有哪些線程

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

jps -l --查看詳細的線程名稱

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

    啓動項目:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

jinfo -flag MaxHeapSize 22244 --查看jvm參數

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

jinfo -flags 22244 --查看jvm所有的參數

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

jstat 查看JVM統計信息--JIT編譯jstat -compiler 22244
--類裝載jstat -class 22244 1000 10
--垃圾回收jstat -gc 22244 1000 3

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

#如何導出內存映象文件#內存溢出字段導出-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=./
#使用jmap命令手動導出:jmap -dump:format=b,file=heap.hprof 22244
#查看死鎖或者線程的狀態jstack 22244 > 22244.txt
#使用jdk裏面的jvisualvm 監控Java程序:
--可以監控本地tomcat,也可以遠程,但是必須開發IP地址和端口。
#生成字節碼javap -verbose Test.class > test.txt

 

    docker環境下怎麼查看項目JVM信息,通過上面的命令?

 

    首先對spring項目進行打包:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

    同時創建一個Dockerfile文件和把Jar上傳到服務上:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

# Dockerfile文件內容FROM openjdk:alpine 
RUN echo "Asia/Shanghai" > /etc/timezone#RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtimeENV TZ=Asia/Shanghai# 添加項目構建出來的 Jar 包ADD spring-0.0.1-SNAPSHOT.jar /root/bin/starter.jar
WORKDIR /root/bin
EXPOSE 8080
# 設定鏡像的主命令CMD ["java", "-jar", "starter.jar", "--spring.profiles.active=dev", "-Djava.security.egd=file:/dev/./urandom"]

    構建鏡像,命令如下:

# 注意後面有個“.”號docker build -t appliaction .

    如果出現如下錯誤,表明是鏡像地址有問題:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    構建成功的鏡像,如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    啓動容器,命令如下:

docker run  -d -p 8080:8080 appliaction

    執行上面的命令,結果如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    一直報這個錯誤:

1: Unable to get pid of LinuxThreads manager thread

    很讓人無語,pid線程爲1明明存在,就是說get不到,然後查詢資料,百度說是openjdk不支持上面哪些命令,這怎麼可能。然後去官網找了資料,說是要把Dockfile的啓動方式改爲下面的:

ENTRYPOINT ["/bin/bash", "-c", "set -e && java -Xmx100m -jar /demo.jar"]

    這個方式我沒有試過,因爲不想動Dockerfile。所以就繼續查找,官網說,這其實不是什麼 Bug,而是 Docker 自 1.10 版本開始加入的安全特性。類似於 jmap 這些 JDK 工具依賴於 Linux 的 PTRACE_ATTACH,而是 Docker 自 1.10 在默認的 seccomp 配置文件中禁用了 ptrace。

    這篇文章介紹了整個的緣由以及應對方法:

JVM in Docker and PTRACE_ATTACH(

https://jarekprzygodzki.wordpress.com/2016/12/19/jvm-in-docker-and-ptrace_attach/)

 

    解決方案如下,主要提及三種:

 

    1.1 –security-opt seccomp=unconfined

    簡單暴力(不推薦),直接關閉 seccomp 配置。用法:

docker run --security-opt seccomp:unconfined ...

    1.2 –cap-add=SYS_PTRACE

    使用 --cap-add 明確添加指定功能:

docker run --cap-add=SYS_PTRACE ...

    1.3 Docker Compose 的支持

    Docker Compose 自 version 1.1.0 (2015-02-25) 起支持 cap_add。官方文檔:cap_add, cap_drop。用法:

     docker-compose.yml 改寫後文件內容如下(內容部分省略):

version: '2'services:  mysql:    ...  api:    ...    cap_add:      - SYS_PTRACE

    所以,我打算採用下面的方式:

docker run --cap-add=SYS_PTRACE -d -p 8080:8080 appliaction

    得出的結論還是,同樣的結果:

    1: Unable to get pid of LinuxThreads manager thread

    最後思考了一下,能不能把pid爲1,改成不是1,改成項目的那樣,22244。然後怎麼設置docker 程序的pid呢?命令如下:

docker run --security-opt seccomp:unconfined -d -p 8080:8080 --pid=host appliaction

    生成內存文件,OK。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

02、怎麼分析內存泄露?

1.調用spring項目中的接口:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    然後,使用top命令進行查看,內存CPU已經爆炸了,結果如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    哪怎麼導出,項目的內存相關信息的文件呢?

    首先,使用命令生成內存溢出的文件:

jmap -dump:format=b,file=heap1.hprof 23427

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    然後,把文件拷貝到服務器上,如下:

docker cp 438559ba685e:/root/bin/heap1.hprof /usr/local/docker/appliaction/heap1.hprof

    回到最初的問題,我們怎麼分析內存泄露文件呢?

    我們將使用Eclipse Memory Analysis進行分析內存文件。下載地址:

http://www.eclipse.org/mat/downloads.php

    

    第二步:開始分析hprof文件

    導入成功之後,如下顯示:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    將hprof文件導入Eclipse Memory Analyzer可看到上圖:

1.Dominator Tree:可以列出佔用內存最大的線程,以及線程下面的那些對象佔用的空間

2.Leak Suspects:MA分析出的可能導致內存溢出的地方。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    通過Dominator Tree,我們可以看到,MemoryController方法上有問題,user對象太多導致內存溢出,CPU飆升。

    Shallow Heap :一個對象所佔用的內存,不包含對其他對象的引用

    Retained Heap :是shallow Heap的總和(單個對象佔用內存*此對象的個數),也就是該對象被GC之後所能回收到內存的總和分析方式:

1.查找線程下佔用內存較大的對象(上圖右邊)

2.定位對象在代碼裏出現的位置(上圖左邊)

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    第四步:Leak Suspects

    查看線程的相關日誌,定位導致內存溢出的代碼位置。

    通過圖形知道,在MemoryController上有個userList上內存暴漲。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    代碼如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    上面的代碼就是用來做測試的,沒有分析的必要。項目實際中,還需要分析爲啥會出這個問題。

    內存死鎖的分析也是這樣的。

jstack 23427 > 23427.txt
docker cp 438559ba685e:/root/bin/23427.txt /usr/local/docker/appliaction/23427.txt
 sz 23427.txt

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

 

 

03、使用jdk的jvisualvm查看使用內存使用情況

    第一步,找到jdk的安裝目錄,如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    啓動項目,本地查看。找到相對應的pid,如果是多個項目中,我們可以查看jvm的相關參數等信息,還可以查看圖形化界面。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    還可以下載堆內存等信息。

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1

    執行以下程序,內存飆升:

http://localhost:8080/heap
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

    點擊堆,也可以查看是哪個user,出現了問題。

    

    如果是實際項目呢?我們可以選擇遠程連接,只要是Java項目的都可以:

    如果出現了以下,錯誤:

    需要在構建的時候,指定環境變量,如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章