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

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

分享一篇公司小夥伴的關於jvm佔用記憶體的技術文章

導讀: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,但並不影響此結論) 

那麼可能是java程序本身的原因嗎?

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

 

檢視此程序記憶體佔用情況:

明顯已經設定了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:

 

具體的參考資料 /

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

 

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

完整見:https://www.analysys.cn/article/detail/20019016