Java 堆 - 這是 JVM 用來分配 java 對象的內(nèi)存。java 堆內(nèi)存的最大值用 java 命令行中的 .Xmx 標志來指定。如果未指定最大的堆大小,那么該極限值由 JVM 根據(jù)諸如計算機中的物理內(nèi)存量和該時刻的可用空閑內(nèi)存量這類因素來決定。始終建議您指定最大的 java 堆值。本地內(nèi)存 - 這是 JVM 用于其內(nèi)部操作的內(nèi)存。JVM 將使用的本地內(nèi)存堆數(shù)量取決于生成的代碼量、創(chuàng)建的線程、GC 期間用于保存 java 對象信息的內(nèi)存,以及在代碼生成、優(yōu)化等過程中使用的臨時空間。
如果有一個第三方本地模塊,那么它也可能使用本地內(nèi)存。例如,本地 JDBC 驅(qū)動程序?qū)⒎峙浔镜貎?nèi)存。
最大本地內(nèi)存量受到任何特定操作系統(tǒng)上的虛擬進程大小限制的約束,也受到用 .Xmx 標志指定用于 java 堆的內(nèi)存量的限制。例如,如果應用程序能分配總計為 3 GB 的內(nèi)存量,并且最大 java 堆的大小為 1 GB,那么本地內(nèi)存量的最大值可能在 2 GB 左右。
進程大小 - 進程大小將是 java 堆、本地內(nèi)存與加載的可執(zhí)行文件和庫所占用內(nèi)存的總和。在 32 位操作系統(tǒng)上,進程的虛擬地址空間最大可達到 4 GB。從這 4 GB 內(nèi)存中,操作系統(tǒng)內(nèi)核為自己保留一部分內(nèi)存(通常為 1 - 2 GB)。剩余內(nèi)存可用于應用程序。
Windows缺省情況下,2 GB 可用于應用程序,剩余 2 GB 保留供內(nèi)核使用。但是,在 Windows 的一些變化版本中,有一個 /3GB 開關可用于改變該分配比率,使應用程序能夠獲得 3 GB。有關 /3GB 開關的詳細信息,可以在以下網(wǎng)址中找到:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ddtools/hh/ddtools/bootini_1fcj.asp
RH Linux AS 2.1 - 3 GB 可用于應用程序。
對于其它操作系統(tǒng),請參考操作系統(tǒng)文檔了解有關配置。
每個進程都獲得其自有的地址空間。在 32 位操作系統(tǒng)中,此地址空間范圍為 0 到 4 GB。此范圍與計算機的可用隨機存取內(nèi)存 (RAM) 或交換空間無關。計算機中的可用物理內(nèi)存總量是該計算機上的可用 RAM 和交換空間之和。所有運行的進程共享這些物理內(nèi)存。
進程內(nèi)的存儲地址是虛擬地址。內(nèi)核將此虛擬地址映射到物理地址上。物理地址指向物理內(nèi)存中的某個位置。在任一給定時間,計算機中運行進程所使用的全部虛擬內(nèi)存的總和不能超過該計算機上可用物理內(nèi)存的總量。
為什么會發(fā)生 OOM 問題,JVM 在這種情況下如何處理?
java 堆中的內(nèi)存不足
如果 JVM 不能在 java 堆中獲得更多內(nèi)存來分配更多 java 對象,將會拋出 java 內(nèi)存不足 (java OOM) 錯誤。如果 java 堆充滿了活動對象,并且 JVM 無法再擴展 java 堆,那么它將不能分配更多 java 對象。
在這種情況下,JVM 讓應用程序決定在拋出 java.lang.OutOfMemoryError 后該執(zhí)行什么操作。例如,應用程序可以處理此錯誤,并決定以安全方式自行關閉或決定忽略此錯誤。如果應用程序不處理此錯誤,那么拋出此錯誤的線程將退出(如果您進行 java Thread Dump,那么將看不到該線程)。
在使用 Weblogic Server 的情況下,如果此錯誤是由某個執(zhí)行線程拋出的,則會處理此錯誤并將其記錄在日志中。如果連續(xù)拋出此錯誤,那么核心運行狀況監(jiān)視器線程將關閉 Weblogic Server。
本地堆中的內(nèi)存不足
如果 JVM 無法獲得更多本地內(nèi)存,它將拋出本地內(nèi)存不足(本地 OOM)錯誤。當進程到達操作系統(tǒng)的進程大小限值,或者當計算機用完 RAM 和交換空間時,通常會發(fā)生這種情況。
當發(fā)生這種情況時,JVM 處理本地 OOM 狀態(tài),記錄說明它已用完本地內(nèi)存或無法獲得內(nèi)存的消息,然后退出。如果 JVM 或加載的任何其它模塊(如 libc 或第三方模塊)不處理這個本地 OOM 狀態(tài),那么操作系統(tǒng)將給 JVM 發(fā)送命令 JVM 退出的 sigabort 信號。通常情況下,JVM 收到 sigabort 信號時將會生成一個核心文件。
請注意,上述消息僅發(fā)送到 stdout 或 stderr 中,而不發(fā)送到應用程序特定的日志文件(如 weblogic.log)
完整 GC 運行:
執(zhí)行一次完整 GC 運行,并且刪除了所有不可及對象以及虛可及、弱可及、軟可及對象,并回收了那些空間。有關不同級別的對象可及性的詳細信息,可以在以下網(wǎng)址中可找到:http://java.sun.com/developer/technicalArticles/ALT/RefObj
您可以檢查是否在發(fā)出 OOM 消息之前執(zhí)行了完整 GC 運行。當完成一次完整 GC 運行時,將會打印類似如下消息(格式取決于 JVM - 請查看 JVM 幫助信息以了解有關格式)
[memory ] 7.160: GC 131072K->130052K (131072K) in 1057.359 ms
以上輸出的格式如下(備注:在此模式下將全部使用相同的格式):
[memory ] <start>: GC <before>K-><after>K (<heap>K), <total> ms
[memory ] <start> - start time of collection (seconds since jvm start)
[memory ] <before> - memory used by objects before collection (KB)
[memory ] <after> - memory used by objects after collection (KB)
[memory ] <heap> - size of heap after collection (KB)
[memory ] <total> - total time of collection (milliseconds)
但是,沒有辦法斷定是否使用 verbose 消息刪除了軟/弱/虛可及的對象。如果您懷疑在拋出 OOM 時這些對象仍然存在,請與 JVM 供應商聯(lián)系。
如果垃圾回收算法是一種按代回收算法(對于 Jrockit 為 gencopy 或 gencon,對于其它 JDK 則是缺省算法),您也將看到類似如下的 verbose 輸出:
[memory ] 2.414: Nursery GC 31000K->20760K (75776K), 0.469 ms
以上是 nursery GC(即 young GC)周期,它將把活動對象從 nursery(或 young 空間)提升到 old 空間。這個周期對我們的分析不重要。有關按代回收算法的詳細信息,可以在 JVM 文檔中找到。
如果在 java OOM 之前未發(fā)生 GC 周期,那么這是一個 JVM 錯誤。
完全壓縮:
確保 JVM 執(zhí)行了適當?shù)膲嚎s工作,并且內(nèi)存并未成碎片(否則會阻止分配大對象并觸發(fā) java OOM 錯誤)。
Java 對象要求內(nèi)存是連續(xù)的。如果可用空閑內(nèi)存是一些碎片,那么 JVM 將無法分配大對象,因為它可能無法放入任何可用空閑內(nèi)存塊中。在這種情況下,JVM 將執(zhí)行一次完全壓縮,以便形成更多連續(xù)的空閑內(nèi)存來容納大對象。
壓縮工作包括在 java 堆內(nèi)存中將對象從一個位置移動到另一個位置,以及更新對這些對象的引用以指向新位置。除非確有必要,否則 JVM 不會壓縮所有對象。這是為了減少 GC 周期的暫停時間。
我們可以通過分析 verbose gc 消息來檢查 java OOM 是否由碎片引起。如果您看到類似如下的輸出(在此無論是否有可用的空閑 java 堆都會拋出 OOM),那么這就是由碎片引起的。
[memory ] 8.162: GC 73043K->72989K (131072K) in 12.938 ms
[memory ] 8.172: GC 72989K->72905K (131072K) in 12.000 ms
[memory ] 8.182: GC 72905K->72580K (131072K) in 13.509 ms
java.lang.OutOfMemoryError在上述情況中您可以看到,所指定的最大堆內(nèi)存是 128MB,并且當實際內(nèi)存使用量僅為 72580K 時,JVM 拋出 OOM。堆使用量僅為 55%。因此在這種情況下,碎片影響是:即使還有 45% 的空閑堆,內(nèi)存也會拋出 OOM。這是一個 JVM 錯誤或缺陷。您應當與 JVM 供應商聯(lián)系。
Java 軟引用也可用于數(shù)據(jù)緩存,當 JVM 用完 java 堆時,可以保證刪除軟可及對象。
應當注意,指定的最大堆內(nèi)存量(在 java 命令行中使用 Xmx 標志)與應用程序的實際 java 堆使用量無關,其在 JVM 啟動時被保留,并且此保留內(nèi)存不能用于其它任何用途。
在使用 Jrockit 時,使用 -verbose 來代替 -verbosegc,因為這可以提供 codegen 信息以及 GC 信息。
在 Windows 環(huán)境下,使用下列步驟來監(jiān)視虛擬進程大?。?/font>
在“開始” -> “運行”對話框中,輸入“perfmon”并單擊“確定”。 在彈出的“性能”窗口中,單擊“+”按鈕(圖表上部)。 在顯示的對話框中選擇下列選項:
性能對象:進程(不是缺省的處理器) 從列表中選擇計數(shù)器:虛擬字節(jié)數(shù) 從列表中選擇實例:選擇 JVM (java) 實例 單擊“添加”,然后單擊“關閉”
在 Unix 或 Linux 環(huán)境下,對于一個給定 PID,可以使用以下命令來查找虛擬內(nèi)存大小 - ps -p <PID> -o vsz。
在 Linux 環(huán)境下,單個 JVM 實例內(nèi)的每個 java 線程都顯示為一個獨立的進程。如果我們獲得根 java 進程的 PID,那么這就足夠了??梢允褂?ps 命令的 .forest 選項來找到根 java 進程。例如,ps lU <user> --forest 將提供一個由指定用戶啟動的所有進程的 ASCII 樹圖。您可以從該樹圖中找到根 java。
調(diào)整 java 堆
如果 java 堆使用量完全在最大堆范圍內(nèi),則減小 java 最大堆將為 JVM 提供更多的本地內(nèi)存。這不是一個解決辦法,而是一個可嘗試的變通方法。由于操作系統(tǒng)限制進程大小,我們需要在 java 堆和本地堆之間尋求一個平衡。
為了縮小問題的范圍,可嘗試禁用運行時優(yōu)化,并檢查這是否會產(chǎn)生任何效果。
如果在整個運行過程中,本地內(nèi)存使用量繼續(xù)不斷增加,那么這可能是本地代碼中的內(nèi)存泄漏。
檢查應用程序是否使用一些 JNI 代碼。這也可能造成本地內(nèi)存泄漏,如果可能的話,您可以嘗試在沒有 JNI 代碼的情況下運行應用程序。
聯(lián)系客服