jvm瘋狂吞佔內存,罪魁禍首是誰?

導讀java

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


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

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



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

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




top命令查看該進程佔用內存狀況(能夠看到此進程已經佔用2.7G物理內存)springboot




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

佔內存狀況RES:1.8G, VIRT:33.4G …架構




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

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


1. 什麼是RES和VIRT?jvm

  • 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 cores * 8個arena。假設機器是8核的,那麼最多能夠有8 * 8 = 64個arena,也就是會使用64 * 64 = 4096M內存。

然而咱們能夠經過設置系統環境變量來改變arena的數量:

export MALLOC_ARENA_MAX=8(通常建議配置程序cpu核數)

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




總結:這裏只是提供一種解決問題的思路,僅供參考;通常咱們遇到問題以後, 首先想到的是否是程序有問題,而後跟蹤了好久仍是未找到問題根本緣由;幾經周折, 才發現問題是出如今最容易被咱們忽視的地方(好比這裏的腳本命令問題)!


歡迎工做一到五年的Java工程師朋友們加入Java程序員開發: 854393687 

羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用"沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代! 

相關文章
相關標籤/搜索