1. 程式人生 > >jvm瘋狂吞佔記憶體,罪魁禍首是誰?

jvm瘋狂吞佔記憶體,罪魁禍首是誰?

jvm瘋狂吞佔記憶體,罪魁禍首是誰?

導讀# JVM是Java Virtual Machine的縮寫,中文名為Java虛擬機器。它是一種用於計算裝置的規範,是一個虛構出來的計算機,主要通過在實際的計算機上模擬模擬各種計算機功能來實現的。在實際運用過程中,易觀技術人員注意到一臺開發機上各個微服務程序佔用記憶體很高,隨即便展開了調查…

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

Centos6.10+Jdk1.8+SpringBoot1.4.4環境下各個JVM程序所佔記憶體使用情況
VIRT和RES都很高......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分鐘之後擷取的)
在這裡插入圖片描述
在這裡插入圖片描述
所以從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 cores8個arena。假設機器是8核的,那麼最多可以有88 = 64個arena,也就是會使用64*64 = 4096M記憶體。

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

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

配置環境變數使其生效,再重啟該jvm程序,VIRT比之前少了快2個G:
在這裡插入圖片描述
/ 具體的參考資料 /
https://access.redhat.com/documentation/enus/red_hat_enterprise_linux/6/html/6.0_release_notes/compiler
https://code.woboq.org/userspace/glibc/malloc/arena.c.html

總結:這裡只是提供一種解決問題的思路,僅供參考;一般我們遇到問題之後, 首先想到的是不是程式有問題,然後跟蹤了很久還是未找到問題根本原因;幾經周折, 才發現問題是出現在最容易被我們忽視的地方(比如這裡的指令碼命令問題)!當然,每個公司或者每個人遇到的問題都不太一樣,需要具體問題具體分析。

作者:唐治彬
北京易觀智庫網路科技有限公司
在這裡插入圖片描述