簡體   English   中英

Linux下Java的虛擬內存使用,使用了太多內存

[英]Virtual Memory Usage from Java under Linux, too much memory used

我有一個在 Linux 下運行的 Java 應用程序的問題。

當我使用默認的最大堆大小 (64 MB) 啟動應用程序時,我看到使用 tops 應用程序為應用程序分配了 240 MB 的虛擬內存。 這會導致計算機上的一些其他軟件出現一些問題,這些軟件相對資源有限。

據我所知,無論如何都不會使用保留的虛擬內存,因為一旦我們達到堆限制,就會拋出OutOfMemoryError 我在 windows 下運行了相同的應用程序,我看到虛擬內存大小和堆大小相似。

無論如何,我可以為Linux下的Java進程配置正在使用的虛擬內存嗎?

編輯 1 :問題不在於堆。 問題是,如果我設置一個 128 MB 的堆,例如,Linux 仍然分配 210 MB 的虛擬內存,這是不需要的,永遠。**

編輯 2 :使用ulimit -v允許限制虛擬內存量。 如果設置的大小低於 204 MB,那么即使不需要 204 MB,只需要 64 MB,應用程序也不會運行。 所以我想了解為什么Java需要這么多的虛擬內存。 這可以改變嗎?

編輯 3 :系統中還有其他幾個應用程序在運行,這些應用程序是嵌入式的。 並且系統確實有虛擬內存限制(來自評論,重要細節)。

這是 Java 長期以來的抱怨,但它在很大程度上毫無意義,而且通常基於查看錯誤的信息。 通常的措辭是“Java 上的 Hello World 需要 10 兆字節!為什么需要那個?” 好吧,這里有一種方法可以讓 64 位 JVM 上的 Hello World 占用超過 4 GB 的空間……至少通過一種測量形式。

java -Xms1024m -Xmx4096m com.example.Hello

測量內存的不同方法

在 Linux 上, top命令為您提供了幾個不同的內存數字。 以下是關於 Hello World 示例的說明:

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 2120 kgregory  20   0 4373m  15m 7152 S    0  0.2   0:00.10 java
  • VIRT 是虛擬內存空間:虛擬內存映射中所有內容的總和(見下文)。 它基本上沒有意義,除非它不是(見下文)。
  • RES 是常駐集大小:當前駐留在 RAM 中的頁數。 在幾乎所有情況下,這是您在說“太大”時應該使用的唯一數字。 但這仍然不是一個很好的數字,尤其是在談論 Java 時。
  • SHR 是與其他進程共享的常駐內存量。 對於 Java 進程,這通常僅限於共享庫和內存映射 JAR 文件。 在這個例子中,我只運行了一個 Java 進程,所以我懷疑 7k 是操作系統使用的庫的結果。
  • 默認情況下未打開 SWAP,此處未顯示。 它表示當前駐留在磁盤上的虛擬內存量,無論它實際上是否在交換空間中 操作系統非常擅長將活動頁面保留在 RAM 中,交換的唯一方法是 (1) 購買更多內存,或 (2) 減少進程數,因此最好忽略此數字。

Windows 任務管理器的情況稍微復雜一些。 在 Windows XP 下,有“Memory Usage”和“Virtual Memory Size”列,但官方文檔沒有說明它們的含義。 Windows Vista 和 Windows 7 添加了更多的列,並且它們實際上被記錄在案 其中,“工作集”測量是最有用的; 它大致對應於 Linux 上的 RES 和 SHR 的總和。

了解虛擬內存映射

進程消耗的虛擬內存是進程內存映射中所有內容的總和。 這包括數據(例如,Java 堆),但也包括程序使用的所有共享庫和內存映射文件。 在 Linux 上,您可以使用pmap命令查看映射到進程空間中的所有內容(從這里開始,我將只參考 Linux,因為它是我使用的;我確定有等效的工具可用於視窗)。 這是“Hello World”程序內存映射的摘錄; 整個內存映射長達100多行,千行列表也不少見。

0000000040000000     36K r-x--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000      8K rwx--  /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000    676K rwx--    [ anon ]
00000006fae00000  21248K rwx--    [ anon ]
00000006fc2c0000  62720K rwx--    [ anon ]
0000000700000000 699072K rwx--    [ anon ]
000000072aab0000 2097152K rwx--    [ anon ]
00000007aaab0000 349504K rwx--    [ anon ]
00000007c0000000 1048576K rwx--    [ anon ]
...
00007fa1ed00d000   1652K r-xs-  /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000   1024K rwx--    [ anon ]
00007fa1ed2d3000      4K -----    [ anon ]
00007fa1ed2d4000   1024K rwx--    [ anon ]
00007fa1ed3d4000      4K -----    [ anon ]
...
00007fa1f20d3000    164K r-x--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000   1020K -----  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000     28K rwx--  /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000   1576K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000   2044K -----  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000     16K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000      4K rwx--  /lib/x86_64-linux-gnu/libc-2.13.so
...

格式的快速解釋:每一行都以段的虛擬內存地址開始。 接下來是段大小、權限和段的來源。 最后一項是文件或“匿名”,表示通過mmap分配的內存塊。

從頂部開始,我們有

  • JVM 加載程序(即,當您鍵入java時運行的程序)。 這是非常小的; 它所做的只是加載到存儲真實 JVM 代碼的共享庫中。
  • 一堆持有 Java 堆和內部數據的匿名塊。 這是一個Sun JVM,所以堆被分成多代,每一代都是自己的內存塊。 注意JVM根據-Xmx值分配虛擬內存空間; 這允許它有一個連續的堆。 -Xms值在內部用於說明程序啟動時“正在使用”的堆有多少,並在接近該限制時觸發垃圾收集。
  • 內存映射 JARfile,在本例中是保存“JDK 類”的文件。 當您對 JAR 進行內存映射時,您可以非常有效地訪問其中的文件(而不是每次從頭開始讀取)。 Sun JVM 將對類路徑上的所有 JAR 進行內存映射; 如果您的應用程序代碼需要訪問 JAR,您還可以對其進行內存映射。
  • 兩個線程的每線程數據。 1M 塊是線程堆棧。 我對 4k 塊沒有很好的解釋,但 @ericsoe 將其標識為“保護塊”:它沒有讀/寫權限,因此如果訪問會導致段錯誤,JVM 會捕獲並翻譯它到StackOverFlowError 對於真正的應用程序,您會看到數十個甚至數百個這些條目在內存映射中重復。
  • 保存實際 JVM 代碼的共享庫之一。 其中有幾個。
  • C 標准庫的共享庫。 這只是 JVM 加載的許多不屬於 Java 的嚴格內容之一。

共享庫特別有趣:每個共享庫至少有兩個段:一個包含庫代碼的只讀段,一個包含庫的全局每進程數據的讀寫段(我不知道是什么)沒有權限的段是;我只在 x64 Linux 上看到過)。 庫的只讀部分可以在使用庫的所有進程之間共享; 例如, libc有 1.5M 的虛擬內存空間可以共享。

虛擬內存大小何時重要?

虛擬內存映射包含很多東西。 有些是只讀的,有些是共享的,有些是分配的但從未接觸過(例如,在這個例子中幾乎所有的 4Gb 堆)。 但是操作系統足夠聰明,只加載它需要的東西,所以虛擬內存大小在很大程度上無關緊要。

如果您在 32 位操作系統上運行,虛擬內存大小很重要,您只能分配 2Gb(或在某些情況下,3Gb)的進程地址空間。 在這種情況下,您正在處理稀缺資源,並且可能必須進行權衡,例如減少堆大小以對大文件進行內存映射或創建大量線程。

但是,鑒於 64 位機器無處不在,我認為不久之后虛擬內存大小將成為一個完全無關的統計數據。

駐留集大小何時重要?

駐留集大小是實際在 RAM 中的那部分虛擬內存空間。 如果您的 RSS 增長到您的總物理內存的很大一部分,則可能是時候開始擔心了。 如果您的 RSS 增長到占用您所有的物理內存,並且您的系統開始交換,那么該擔心的時間已經過去了。

但是 RSS 也具有誤導性,尤其是在負載較輕的機器上。 操作系統不會花費大量精力來回收進程使用的頁面。 這樣做幾乎沒有什么好處,而且如果進程在將來接觸到頁面,可能會發生代價高昂的頁面錯誤。 因此,RSS 統計信息可能包含許多未處於活動狀態的頁面。

底線

除非您正在交換,否則不要過分擔心各種內存統計信息告訴您什么。 需要注意的是,不斷增長的 RSS 可能表明某種內存泄漏。

對於 Java 程序,關注堆中發生的事情要重要得多。 消耗的空間總量很重要,您可以采取一些步驟來減少它。 更重要的是你花在垃圾收集上的時間,以及堆的哪些部分被收集。

訪問磁盤(即數據庫)很昂貴,而內存很便宜。 如果你可以用一個換另一個,那就這樣做吧。

Java 和 glibc >= 2.10(包括 Ubuntu >= 10.04,RHEL >= 6)存在一個已知問題。

解決方法是設置這個環境。 多變的:

export MALLOC_ARENA_MAX=4

如果您正在運行 Tomcat,您可以將其添加到TOMCAT_HOME/bin/setenv.sh文件中。

對於 Docker,將此添加到 Dockerfile

ENV MALLOC_ARENA_MAX=4

有一篇關於設置 MALLOC_ARENA_MAX 的 IBM 文章https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

這篇博文說

眾所周知,常駐內存會以類似於內存泄漏或內存碎片的方式蔓延。

還有一個開放的 JDK 錯誤JDK-8193521“glibc 使用默認配置浪費內存”

在 Google 或 SO 上搜索 MALLOC_ARENA_MAX 以獲取更多參考。

您可能還想調整其他 malloc 選項以優化已分配內存的低碎片化:

# tune glibc memory allocation, optimize for low fragmentation
# limit the number of arenas
export MALLOC_ARENA_MAX=2
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt"
export MALLOC_MMAP_THRESHOLD_=131072
export MALLOC_TRIM_THRESHOLD_=131072
export MALLOC_TOP_PAD_=131072
export MALLOC_MMAP_MAX_=65536

為 Java 進程分配的內存量與我預期的差不多。 我在嵌入式/內存有限的系統上運行 Java 時遇到過類似的問題。 在具有任意 VM 限制或在沒有足夠交換量的系統上運行任何應用程序往往會中斷。 這似乎是許多現代應用程序的本質,它們不是為在資源有限的系統上使用而設計的。

您還有更多選項可以嘗試限制 JVM 的內存占用。 這可能會減少虛擬內存占用:

-XX:ReservedCodeCacheSize=32m 保留代碼緩存大小(以字節為單位)- 最大代碼緩存大小。 [Solaris 64 位、amd64 和 -server x86:48m; 在 1.5.0_06 及更早版本中,Solaris 64 位和 and64:1024m。]

-XX:MaxPermSize=64m 永久代的大小。 [5.0 及更新版本:64 位虛擬機擴展了 30%; 1.4 amd64:96m; 1.3.1 - 客戶端:32m。]

此外,您還應該將 -Xmx(最大堆大小)設置為盡可能接近應用程序實際峰值內存使用量的值。 我相信 JVM 的默認行為仍然是每次將堆大小擴展到最大值時將堆大小加倍 如果您從 32M 堆開始並且您的應用程序達到 65M,那么堆最終會增長 32M -> 64M -> 128M。

您也可以嘗試使用此方法來降低 VM 在堆增長方面的積極性:

-XX:MinHeapFreeRatio=40 GC 后堆空閑的最小百分比以避免擴展。

此外,從我幾年前的試驗中回憶起,加載的本機庫的數量對最小占用空間產生了巨大影響。 如果我沒記錯的話,加載 java.net.Socket 增加了超過 15M(我可能沒有)。

Sun JVM 需要大量內存用於 HotSpot,它映射到共享內存中的運行時庫。

如果內存是一個問題,請考慮使用另一個適合嵌入的 JVM。 IBM 有 j9,還有使用 GNU 類路徑庫的開源“jamvm”。 此外,Sun 在 SunSPOTS 上運行了 Squeak JVM,因此還有其他選擇。

減少資源有限的系統的堆空間的一種方法可能是使用 -XX:MaxHeapFreeRatio 變量。 這通常設置為 70,是 GC 收縮前空閑堆的最大百分比。 將其設置為較低的值,您將在例如 jvisualvm 分析器中看到較小的堆通常用於您的程序。

編輯:要為 -XX:MaxHeapFreeRatio 設置較小的值,您還必須設置 -XX:MinHeapFreeRatio 例如

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld

EDIT2:為啟動並執行相同任務的真實應用程序添加了一個示例,一個使用默認參數,一個使用 10 和 25 作為參數。 我沒有注意到任何真正的速度差異,盡管理論上 java 應該使用更多時間來增加后一個例子中的堆。

默認參數

最后,最大堆為 905,使用的堆為 378

最小堆 10,最大堆 25

最后,最大堆為 722,使用的堆為 378

這實際上有一些影響,因為我們的應用程序運行在遠程桌面服務器上,許多用戶可能會同時運行它。

只是一個想法,但您可以檢查ulimit -v選項的影響。

這不是一個實際的解決方案,因為它會限制所有進程可用的地址空間,但這將允許您使用有限的虛擬內存檢查應用程序的行為。

Sun 的 java 1.4 有以下參數來控制內存大小:

-Xmsn 指定內存分配池的初始大小(以字節為單位)。 該值必須是大於 1MB 的 1024 的倍數。 附加字母 k 或 K 以指示千字節,或附加字母 m 或 M 以指示兆字節。 默認值為 2MB。 例子:

 -Xms6291456 -Xms6144k -Xms6m

-Xmxn 指定內存分配池的最大大小(以字節為單位)。 該值必須是大於 2MB 的 1024 的倍數。 附加字母 k 或 K 以指示千字節,或附加字母 m 或 M 以指示兆字節。 默認值為 64MB。 例子:

 -Xmx83886080 -Xmx81920k -Xmx80m

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5 和 6 還有一些。 請參閱http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp

不,您無法配置 VM 所需的內存量。 但是,請注意,這是虛擬內存,而不是常駐內存,因此如果不實際使用,它只會留在那里而不會造成傷害。

或者,您可以嘗試一些其他的 JVM,然后是 Sun 一個,內存占用更小,但我不能在這里建議。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM