Java使用比堆大小更多的內(nèi)存(或正確的Docker內(nèi)存限制大?。?/h1>
4 回答

TA貢獻(xiàn)1770條經(jīng)驗(yàn) 獲得超3個(gè)贊
Java進(jìn)程使用的虛擬內(nèi)存遠(yuǎn)遠(yuǎn)超出了Java堆。您知道,JVM包含許多子系統(tǒng):垃圾收集器,類加載,JIT編譯器等,所有這些子系統(tǒng)都需要一定量的RAM才能運(yùn)行。
JVM不是RAM的唯一消費(fèi)者。本機(jī)庫(kù)(包括標(biāo)準(zhǔn)Java類庫(kù))也可以分配本機(jī)內(nèi)存。而本機(jī)內(nèi)存跟蹤甚至無(wú)法看到這一點(diǎn)。Java應(yīng)用程序本身也可以通過直接ByteBuffers使用堆外內(nèi)存。
那么什么需要Java進(jìn)程中的內(nèi)存?
JVM部件(主要通過本機(jī)內(nèi)存跟蹤顯示)
Java堆
最明顯的部分。這是Java對(duì)象所在的位置。堆占用了
-Xmx
大量的內(nèi)存。垃圾收集器
GC結(jié)構(gòu)和算法需要額外的內(nèi)存用于堆管理。這些結(jié)構(gòu)是Mark Bitmap,Mark Stack(用于遍歷對(duì)象圖),Remembered Sets(用于記錄區(qū)域間引用)等。其中一些是直接可調(diào)的,例如
-XX:MarkStackSizeMax
,其他一些依賴于堆布局,例如,較大的是G1區(qū)域(-XX:G1HeapRegionSize
),較小的是記憶集。GC內(nèi)存開銷因GC算法而異。
-XX:+UseSerialGC
并且-XX:+UseShenandoahGC
開銷最小。G1或CMS可以輕松使用總堆大小的10%左右。代碼緩存
包含動(dòng)態(tài)生成的代碼:JIT編譯的方法,解釋器和運(yùn)行時(shí)存根。它的大小受限于
-XX:ReservedCodeCacheSize
(默認(rèn)為240M)。關(guān)閉-XX:-TieredCompilation
以減少編譯代碼的數(shù)量,從而減少代碼緩存的使用。編譯器
JIT編譯器本身也需要內(nèi)存來完成它的工作。通過關(guān)閉分層編譯或減少編譯器線程的數(shù)量,可以再次減少這種情況:
-XX:CICompilerCount
。類加載
類元數(shù)據(jù)(方法字節(jié)碼,符號(hào),常量池,注釋等)存儲(chǔ)在稱為Metaspace的堆外區(qū)域中。加載的類越多 - 使用的元空間就越多??偸褂昧靠梢允芟?code>-XX:MaxMetaspaceSize(默認(rèn)為無(wú)限制)和
-XX:CompressedClassSpaceSize
(默認(rèn)為1G)。符號(hào)表
JVM的兩個(gè)主要哈希表:Symbol表包含名稱,簽名,標(biāo)識(shí)符等,String表包含對(duì)實(shí)習(xí)字符串的引用。如果本機(jī)內(nèi)存跟蹤指示String表占用大量?jī)?nèi)存,則可能意味著應(yīng)用程序過度調(diào)用
String.intern
。主題
線程堆棧也負(fù)責(zé)占用RAM。堆棧大小由
-Xss
。每個(gè)線程的默認(rèn)值是1M,但幸運(yùn)的是事情并沒有那么糟糕。操作系統(tǒng)懶惰地分配內(nèi)存頁(yè)面,即在第一次使用時(shí),因此實(shí)際內(nèi)存使用量將低得多(通常每個(gè)線程堆棧80-200 KB)。我編寫了一個(gè)腳本來估計(jì)RSS有多少屬于Java線程堆棧。還有其他JVM部件可以分配本機(jī)內(nèi)存,但它們通常不會(huì)在總內(nèi)存消耗中發(fā)揮重要作用。
直接緩沖
應(yīng)用程序可以通過調(diào)用顯式請(qǐng)求堆外內(nèi)存ByteBuffer.allocateDirect
。默認(rèn)的堆外限制等于-Xmx
,但可以用它覆蓋-XX:MaxDirectMemorySize
。Direct ByteBuffers包含在Other
NMT輸出部分(或Internal
JDK 11之前)。
通過JMX可以看到使用的直接內(nèi)存量,例如在JConsole或Java Mission Control中:
除了直接的ByteBuffers,還可以有MappedByteBuffers
- 映射到進(jìn)程虛擬內(nèi)存的文件。NMT不跟蹤它們,但MappedByteBuffers也可以占用物理內(nèi)存。而且沒有一種簡(jiǎn)單的方法來限制它們可以承受多少。您可以通過查看進(jìn)程內(nèi)存映射來查看實(shí)際使用情況:pmap -x <pid>
Address Kbytes RSS Dirty Mode Mapping ... 00007f2b3e557000 39592 32956 0 r--s- some-file-17405-Index.db 00007f2b40c01000 39600 33092 0 r--s- some-file-17404-Index.db ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
本地圖書館
加載的JNI代碼System.loadLibrary
可以根據(jù)需要分配盡可能多的堆外內(nèi)存,而無(wú)需JVM端的控制。這也涉及標(biāo)準(zhǔn)的Java類庫(kù)。特別是,未封閉的Java資源可能成為本機(jī)內(nèi)存泄漏的來源。典型的例子是ZipInputStream
或DirectoryStream
。
JVMTI代理,特別是jdwp
調(diào)試代理 - 也可能導(dǎo)致過多的內(nèi)存消耗。
此答案描述了如何使用async-profiler配置本機(jī)內(nèi)存分配。
分配器問題
進(jìn)程通常直接從OS(通過mmap
系統(tǒng)調(diào)用)或使用malloc
- 標(biāo)準(zhǔn)libc分配器請(qǐng)求本機(jī)內(nèi)存。反過來,malloc
要求OS使用大塊內(nèi)存mmap
,然后根據(jù)自己的分配算法管理這些塊。問題是 - 該算法可能導(dǎo)致碎片和過多的虛擬內(nèi)存使用。
jemalloc
,替代分配器,通??雌饋肀瘸R?guī)libc更智能malloc
,因此切換到jemalloc
可能導(dǎo)致更小的空閑。
結(jié)論
沒有保證估計(jì)Java進(jìn)程的完整內(nèi)存使用量的方法,因?yàn)橛刑嘁蛩匦枰紤]。
Total memory = Heap + Code Cache + Metaspace + Symbol tables + Other JVM structures + Thread stacks + Direct buffers + Mapped files + Native Libraries + Malloc overhead + ...
可以通過JVM標(biāo)志縮小或限制某些內(nèi)存區(qū)域(如代碼緩存),但許多其他內(nèi)存區(qū)域完全不受JVM控制。
設(shè)置Docker限制的一種可能方法是在進(jìn)程的“正常”狀態(tài)下觀察實(shí)際內(nèi)存使用情況。有研究Java內(nèi)存消耗問題的工具和技術(shù):Native Memory Tracking,pmap,jemalloc,async-profiler。

TA貢獻(xiàn)1827條經(jīng)驗(yàn) 獲得超8個(gè)贊
內(nèi)存的詳細(xì)用法由本機(jī)內(nèi)存跟蹤(NMT)詳細(xì)信息(主要是代碼元數(shù)據(jù)和垃圾收集器)提供。除此之外,Java編譯器和優(yōu)化器C1 / C2使用摘要中未報(bào)告的內(nèi)存。
使用JVM標(biāo)志可以減少內(nèi)存占用(但存在影響)。
必須通過測(cè)試應(yīng)用程序的預(yù)期負(fù)載來完成Docker容器大小調(diào)整。
每個(gè)組件的詳細(xì)信息
所述共享類空間可以在容器內(nèi)被禁用,因?yàn)轭惒粫?huì)被另一個(gè)JVM進(jìn)程共享??梢允褂靡韵聵?biāo)志。它將刪除共享類空間(17MB)。
-Xshare:off
所述垃圾收集器串行具有在更長(zhǎng)的暫停時(shí)間期間垃圾的成本最小的內(nèi)存占用收集處理(參照在一個(gè)畫面GC之間阿列克謝Shipil?v比較)??梢允褂靡韵聵?biāo)志啟用它。它可以節(jié)省使用的GC空間(48MB)。
-XX:+UseSerialGC
所述C2編譯器可以用下面的標(biāo)志被禁用,以減少用于決定是否優(yōu)化與否的方法分析數(shù)據(jù)。
-XX:+TieredCompilation -XX:TieredStopAtLevel=1
代碼空間減少了20MB。而且,JVM外部的內(nèi)存減少了80MB(NMT空間和RSS空間之間的差異)。優(yōu)化編譯器C2需要100MB。
的C1和C2的編譯器可以用下面的標(biāo)志被禁用。
-Xint
JVM外部的內(nèi)存現(xiàn)在低于總提交空間。代碼空間減少了43MB。請(qǐng)注意,這會(huì)對(duì)應(yīng)用程序的性能產(chǎn)生重大影響。禁用C1和C2編譯器會(huì)減少170 MB的內(nèi)存使用量。
使用Graal VM編譯器(替換C2)可以減少內(nèi)存占用。它增加了20MB的代碼內(nèi)存空間,并從外部JVM內(nèi)存減少了60MB。
JVM的Java內(nèi)存管理文章提供了不同內(nèi)存空間的一些相關(guān)信息。Oracle在Native Memory Tracking文檔中提供了一些細(xì)節(jié)。有關(guān)高級(jí)編譯策略和禁用C2中的編譯級(jí)別的更多詳細(xì)信息將代碼高速緩存大小減少了5倍。有關(guān)為什么JVM報(bào)告的內(nèi)存比Linux進(jìn)程駐留集大小更多的一些細(xì)節(jié)?當(dāng)兩個(gè)編譯器都被禁用時(shí)。
添加回答
舉報(bào)