spring-boot內存佔用過高

JVM是Java Virtual Machine的縮寫,中文名爲Java虛擬機。它是一種用於計算設備的規範,是一個虛構出來的計算機,主要通過在實際的計算機上仿真模擬各種計算機功能來實現的。在實際運用過程中,易觀技術人員注意到一臺開發機上各個微服務進程佔用內存很高,隨即便展開了調查......

現象:前段時間發現某臺開發機上各個微服務進程佔用內存很高,這裏記錄下解決思路,僅供參考。

  • Centos6.10+Jdk1.8+SpringBoot1.4.4環境下各個JVM進程所佔內存使用情況

VIRT和RES都很高......

以其中某個進程爲例(進程啓動日期爲8月9日,排查時候的時間是8月10日10:54:58,也就是說該進程運行時間應該不會超過48小時)

top命令查看該進程佔用內存情況(可以看到此進程已經佔用2.7G物理內存)

爲了排除掉是因爲中途有壓力測試的嫌疑,將此服務進行了重啓,但是剛起的進程(19146),

佔內存情況RES:1.8G, VIRT:33.4G …

JVM進程動不動就是2G以上的內存,然而開發環境並沒什麼業務請求,誰是罪魁禍首 ?

解決問題之前,先複習下幾個基礎知識。

1. 什麼是RES和VIRT?

  • RES:resident memory usage 常駐內存

(1)進程當前使用的內存大小,但不包括swap out

(2)包含其他進程的共享

(3)如果申請100m的內存,實際使用10m,它只增長10m,與VIRT相反

(4)關於庫佔用內存的情況,它只統計加載的庫文件所佔內存大小

RES = CODE + DATA

  • VIRT:virtual memory usage

(1)進程“需要的”虛擬內存大小,包括進程使用的庫、代碼、數據等

(2)假如進程申請100m的內存,但實際只使用了10m,那麼它會增長100m,而不是實際的使用量

VIRT = SWAP + RES

2. Linux與進程內存模型

3. JVM內存模型(1.7與1.8之間的區別)

所以JVM進程內存大小大致爲:

非heap(非heap=元空間+棧內存+…)+heap+JVM進程運行所需內存+其他數據

那麼會是jvm內存泄漏引起的嗎?
  • 使用Jmap命令將整個heap dump下來,然後用jvisualvm分析

可以看到,堆內存一切正常(dump會引起FGC,但並不影響此結論)

那麼可能是SpringBoot的原因嗎?

爲了驗證此問題,通過部署系統在開發機上起了1個沒有任何業務代碼的springboot進程,僅僅是引入註冊中心

查看此進程內存佔用情況:

明顯已經設置了Xmx爲512MB,雖然Xmx不等於最終JVM所佔總內存,但至少也不會偏差太多; 那麼使用jmap命令查看當前jvm堆內存配置和使用情況(下面的圖2是在圖1現場5分鐘之後截取的)

(圖1)

(圖2)

所以從2次的jmap結果中,可以得出以下幾個結論:

  • 我們的Xmx設置並沒有生效,因爲MaxHeapSize≠Xmx
  • 圖1中jvm佔用內存計算:

元空間(20.79MB)+ eden(834MB)+年老代(21MB)+線程棧(38*1024KB)+JVM進程本身運行內存+ NIO的DirectBuffer +JIT+JNI+…≈top(Res) 1.1G

當前jvm線程數統計:jstack 7311 |grep ‘tid’|wc –l (linux 64位系統中jvm線程默認棧大小爲1MB)

  • Eden區進行了多次擴容,由圖1可知eden區可用空間已經不夠用了(容量:843MB,已使用:834MB),圖2中擴容到1566MB
  • Eden區經歷了Minor Gc,由圖2可知eden區已使用空間:60MB,說明之前在eden區的對象大部分已經被回收,部分未被回收的對象已經轉入到擴展1區了
Xmx設置爲何未生效?

查看部署系統的啓動腳本,發現啓動方式爲:Java –jar $jar_file –Xms512m –Xmx1024m

正確的Java命令:

java [ options ] class [ arguments ]

java [ options ] -jar file.jar [ arguments ]

其實到這裏,也找到了此問題原因所在,Java –jar $jar_file –Xms512m –Xmx1024m被JVM解釋成了程序的參數。

手動執行: java –Xms512m –Xmx1024m –jar ems-client-1.0.jar

至此,RES過高的問題已解決,但是VIRT的問題還在

使用系統命令pmap -x 3516查看進程的內存映射情況,會發現大量的64MB內存塊存在;統計了下,大概有50多個65404+132=65536,正好是64MB,算起來大約3個多G

於是Google之,發現大致的原因是從glibc2.11版本開始,linux爲了解決多線程下內存分配競爭而引起的性能問題,增強了動態內存分配行爲,使用了一種叫做arena的memory pool,在64位系統下面缺省配置是一個arena大小爲64M,一個進程可以最多有cpu cores * 8個arena。假設機器是8核的,那麼最多可以有8 * 8 = 64個arena,也就是會使用64 * 64 = 4096M內存。

然而我們可以通過設置系統環境變量來改變arena的數量:

export MALLOC_ARENA_MAX=8(一般建議配置程序cpu核數)

配置環境變量使其生效,再重啓該jvm進程,VIRT比之前少了快2個G:

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