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