欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關于Spring?Boot內存泄露排查的記錄

 更新時間:2022年06月17日 09:01:27   作者:weixin_42073629  
這篇文章主要介紹了關于Spring?Boot內存泄露排查的記錄,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

在項目遷移到Spring Boot之后,發(fā)生內存使用量過高的問題。本文介紹了整個排查過程以及使用到的工具,也非常適用于其他堆外內存排查。

背景

為了更好地實現對項目的管理,我們將組內一個項目遷移到MDP框架(基于Spring Boot),隨后我們就發(fā)現系統會頻繁報出Swap區(qū)域使用量過高的異常。筆者被叫去幫忙查看原因,發(fā)現配置了4G堆內內存,但是實際使用的物理內存竟然高達7G,確實不正常。

JVM參數配置是:

-XX:MetaspaceSize=256M 
-XX:MaxMetaspaceSize=256M 
-XX:+AlwaysPreTouch 
-XX:ReservedCodeCacheSize=128m 
-XX:InitialCodeCacheSize=128m, 
-Xss512k -Xmx4g -Xms4g,-XX:+UseG1GC 
-XX:G1HeapRegionSize=4M

實際使用的物理內存如下圖所示:

圖片

排查過程

1.使用Java層面的工具定位內存區(qū)域

(堆內內存、Code區(qū)域或者使用unsafe.allocateMemory和DirectByteBuffer申請的堆外內存)

筆者在項目中添加-XX:NativeMemoryTracking=detailJVM參數重啟項目,使用命令jcmd pid VM.native_memory detail查看到的內存分布如下: 

圖片

發(fā)現命令顯示的committed的內存小于物理內存,因為jcmd命令顯示的內存包含堆內內存、Code區(qū)域、通過unsafe.allocateMemory和DirectByteBuffer申請的內存,但是不包含其他Native Code(C代碼)申請的堆外內存。所以猜測是使用Native Code申請內存所導致的問題。

為了防止誤判,筆者使用了pmap查看內存分布,發(fā)現大量64M地址;而這些地址空間不在jcmd命令所給出的地址空間里面,基本上可斷定就是這些64M的內存所導致。

圖片

2. 使用系統層面的工具定位堆外內存

因為筆者已經基本上確定是Native Code所引起,而Java層面的工具不便于排查此類問題,只能使用系統層面的工具去定位問題。

首先,使用了gperftools去定位問題

gperftools的使用方法可以參考gperftools,gperftools的監(jiān)控如下:

圖片

從上圖可以看出:使用malloc申請的內存最高到3G之后就釋放了,之后始終維持在700M-800M。筆者第一反應是:難道Native Code中沒有使用malloc申請,直接使用mmap/brk申請的?(gperftools原理就使用動態(tài)鏈接的方式替換了操作系統默認的內存分配器(glibc)。)

然后,使用strace去追蹤系統調用

因為使用gperftools沒有追蹤到這些內存,于是直接使用命令strace -f -e"brk,mmap,munmap" -p pid追蹤向OS申請內存請求,但是并沒有發(fā)現有可疑內存申請。strace監(jiān)控如下圖所示:

圖片

接著,使用GDB去dump可疑內存

因為使用strace沒有追蹤到可疑內存申請;于是想著看看內存中的情況。

就是直接使用命令gdp -pid pid進入GDB之后,然后使用命令dump memory mem.bin startAddress endAddressdump內存,其中startAddress和endAddress可以從/proc/pid/smaps中查找。

然后使用strings mem.bin查看dump的內容,如下:

圖片

從內容上來看,像是解壓后的JAR包信息。讀取JAR包信息應該是在項目啟動的時候,那么在項目啟動之后使用strace作用就不是很大了。所以應該在項目啟動的時候使用strace,而不是啟動完成之后。

再次,項目啟動時使用strace去追蹤系統調用

項目啟動使用strace追蹤系統調用,發(fā)現確實申請了很多64M的內存空間,截圖如下:

圖片

使用該mmap申請的地址空間在pmap對應如下:

圖片

最后,使用jstack去查看對應的線程

因為strace命令中已經顯示申請內存的線程ID。直接使用命令jstack pid去查看線程棧,找到對應的線程棧(注意10進制和16進制轉換)如下:

圖片

這里基本上就可以看出問題來了:MCC(美團統一配置中心)使用了Reflections進行掃包,底層使用了Spring Boot去加載JAR。因為解壓JAR使用Inflater類,需要用到堆外內存,然后使用Btrace去追蹤這個類,棧如下:

圖片

然后查看使用MCC的地方,發(fā)現沒有配置掃包路徑,默認是掃描所有的包。于是修改代碼,配置掃包路徑,發(fā)布上線后內存問題解決。

3. 為什么堆外內存沒有釋放掉呢?

雖然問題已經解決了,但是有幾個疑問:

  • 為什么使用舊的框架沒有問題?
  • 為什么堆外內存沒有釋放?
  • 為什么內存大小都是64M,JAR大小不可能這么大,而且都是一樣大?
  • 為什么gperftools最終顯示使用的內存大小是700M左右,解壓包真的沒有使用malloc申請內存嗎?

帶著疑問,筆者直接看了一下Spring Boot Loader那一塊的源碼。發(fā)現Spring Boot對Java JDK的InflaterInputStream進行了包裝并且使用了Inflater,而Inflater本身用于解壓JAR包的需要用到堆外內存。而包裝之后的類ZipInflaterInputStream沒有釋放Inflater持有的堆外內存。于是筆者以為找到了原因,立馬向Spring Boot社區(qū)反饋了這個Bug。但是反饋之后,筆者就發(fā)現Inflater這個對象本身實現了finalize方法,在這個方法中有調用釋放堆外內存的邏輯。也就是說Spring Boot依賴于GC釋放堆外內存。

筆者使用jmap查看堆內對象時,發(fā)現已經基本上沒有Inflater這個對象了。于是就懷疑GC的時候,沒有調用finalize。帶著這樣的懷疑,筆者把Inflater進行包裝在Spring Boot Loader里面替換成自己包裝的Inflater,在finalize進行打點監(jiān)控,結果finalize方法確實被調用了。于是筆者又去看了Inflater對應的C代碼,發(fā)現初始化的時候使用了malloc申請內存,end的時候也調用了free去釋放內存。

此刻,筆者只能懷疑free的時候沒有真正釋放內存,便把Spring Boot包裝的InflaterInputStream替換成Java JDK自帶的,發(fā)現替換之后,內存問題也得以解決了。

這時,再返過來看gperftools的內存分布情況,發(fā)現使用Spring Boot時,內存使用一直在增加,突然某個點內存使用下降了好多(使用量直接由3G降為700M左右)。這個點應該就是GC引起的,內存應該釋放了,但是在操作系統層面并沒有看到內存變化,那是不是沒有釋放到操作系統,被內存分配器持有了呢?

繼續(xù)探究,發(fā)現系統默認的內存分配器(glibc 2.12版本)和使用gperftools內存地址分布差別很明顯,2.5G地址使用smaps發(fā)現它是屬于Native Stack。內存地址分布如下:

圖片

到此,基本上可以確定是內存分配器在搗鬼;搜索了一下glibc 64M,發(fā)現glibc從2.11開始對每個線程引入內存池(64位機器大小就是64M內存),原文如下:

圖片

按照文中所說去修改MALLOC_ARENA_MAX環(huán)境變量,發(fā)現沒什么效果。查看tcmalloc(gperftools使用的內存分配器)也使用了內存池方式。

為了驗證是內存池搞的鬼,筆者就簡單寫個不帶內存池的內存分配器。使用命令gcc zjbmalloc.c -fPIC -shared -o zjbmalloc.so生成動態(tài)庫,然后使用export LD_PRELOAD=zjbmalloc.so替換掉glibc的內存分配器。其中代碼Demo如下:

圖片

通過在自定義分配器當中埋點可以發(fā)現其實程序啟動之后應用實際申請的堆外內存始終在700M-800M之間,gperftools監(jiān)控顯示內存使用量也是在700M-800M左右。但是從操作系統角度來看進程占用的內存差別很大(這里只是監(jiān)控堆外內存)。

筆者做了一下測試,使用不同分配器進行不同程度的掃包,占用的內存如下:

圖片

為什么自定義的malloc申請800M,最終占用的物理內存在1.7G呢?

因為自定義內存分配器采用的是mmap分配內存,mmap分配內存需要按需向上取整到整數個頁,所以存在著巨大的空間浪費。通過監(jiān)控發(fā)現最終申請的頁面數目在536k個左右,那實際上向系統申請的內存等于512k * 4k(pagesize) = 2G。 為什么這個數據大于1.7G呢?

因為操作系統采取的是延遲分配的方式,通過mmap向系統申請內存的時候,系統僅僅返回內存地址并沒有分配真實的物理內存。只有在真正使用的時候,系統產生一個缺頁中斷然后再分配實際的物理Page。

總結

圖片

整個內存分配的流程如上圖所示。MCC掃包的默認配置是掃描所有的JAR包。在掃描包的時候,Spring Boot不會主動去釋放堆外內存,導致在掃描階段,堆外內存占用量一直持續(xù)飆升。當發(fā)生GC的時候,Spring Boot依賴于finalize機制去釋放了堆外內存;但是glibc為了性能考慮,并沒有真正把內存歸返到操作系統,而是留下來放入內存池了,導致應用層以為發(fā)生了“內存泄漏”。所以修改MCC的配置路徑為特定的JAR包,問題解決。

筆者在發(fā)表這篇文章時,發(fā)現Spring Boot的最新版本(2.0.5.RELEASE)已經做了修改,在ZipInflaterInputStream主動釋放了堆外內存不再依賴GC;所以Spring Boot升級到最新版本,這個問題也可以得到解決。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • SpringBoot解決BigDecimal傳到前端后精度丟失問題

    SpringBoot解決BigDecimal傳到前端后精度丟失問題

    這篇文章將通過示例詳細為大家介紹SpringBoot如何解決BigDecimal傳到前端后精度丟失問題,文中的示例代碼講解詳細,感興趣的可以了解一下
    2022-06-06
  • java  hibernate使用注解來定義聯合主鍵

    java hibernate使用注解來定義聯合主鍵

    這篇文章主要介紹了java hibernate使用注解來定義聯合主鍵的相關資料,需要的朋友可以參考下
    2017-01-01
  • JAVA如何按字節(jié)截取字符串

    JAVA如何按字節(jié)截取字符串

    這篇文章主要介紹了JAVA如何按字節(jié)截取字符串,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • SpringBoot URL帶有特殊字符([]/{}等),報400錯誤的解決

    SpringBoot URL帶有特殊字符([]/{}等),報400錯誤的解決

    這篇文章主要介紹了SpringBoot URL帶有特殊字符([]/{}等),報400錯誤的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • 10分鐘帶你徒手寫個Java線程池

    10分鐘帶你徒手寫個Java線程池

    我們自己手動實現的線程池要比Java自身的線程池簡單的多,我們去掉了各種復雜的處理方式,只保留了最核心的原理,感興趣的小伙伴可以跟隨小編一起學習一下
    2023-04-04
  • javaWEB中前后臺亂碼問題的解決方法總結

    javaWEB中前后臺亂碼問題的解決方法總結

    下面小編就為大家?guī)硪黄猨avaWEB中前后臺亂碼問題的解決方法總結。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • 基于JWT的spring boot權限驗證技術實現教程

    基于JWT的spring boot權限驗證技術實現教程

    這篇文章主要給大家介紹了關于基于JWT的spring boot權限驗證技術實現的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • Maven模版Bug及解決辦法

    Maven模版Bug及解決辦法

    默認,會幫我們創(chuàng)建src/main/resources 按照Maven的規(guī)范,Maven會有3個目錄,分別是: src/main/java : java源文件存放位置 src/main/resource : resource資源,如配置文件等 src/test/java : 測試代碼源文件存放位置
    2016-04-04
  • java銀行管理系統源碼

    java銀行管理系統源碼

    這篇文章主要為大家詳細介紹了java銀行管理系統源碼,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • Java 讀取圖片的mimeType的方法

    Java 讀取圖片的mimeType的方法

    本篇文章主要介紹了Java 讀取圖片的mimeType的方法,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01

最新評論