JVM內(nèi)存管理:深入Java內(nèi)存區(qū)域與OOM_第1頁
JVM內(nèi)存管理:深入Java內(nèi)存區(qū)域與OOM_第2頁
JVM內(nèi)存管理:深入Java內(nèi)存區(qū)域與OOM_第3頁
JVM內(nèi)存管理:深入Java內(nèi)存區(qū)域與OOM_第4頁
JVM內(nèi)存管理:深入Java內(nèi)存區(qū)域與OOM_第5頁
已閱讀5頁,還剩4頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

Java 與 C 之間有一堵由內(nèi)存動態(tài)分配和垃圾收集技術(shù)所圍成的高墻 墻外面的人想進去 墻里面的人 卻想出來 概述 概述 對于從事 C C 程序開發(fā)的開發(fā)人員來說 在內(nèi)存管理領(lǐng)域 他們即是擁有最高權(quán)力的皇帝又是執(zhí)行最 基礎(chǔ)工作的勞動人民 擁有每一個對象的 所有權(quán) 又擔(dān)負著每一個對象生命開始到終結(jié)的維護責(zé)任 對于 Java 程序員來說 不需要在為每一個 new 操作去寫配對的 delete free 不容易出現(xiàn)內(nèi)容泄漏和內(nèi)存 溢出錯誤 看起來由 JVM 管理內(nèi)存一切都很美好 不過 也正是因為 Java 程序員把內(nèi)存控制的權(quán)力交給了 JVM 一旦出現(xiàn)泄漏和溢出 如果不了解 JVM 是怎樣使用內(nèi)存的 那排查錯誤將會是一件非常困難的事情 VM 運行時數(shù)據(jù)區(qū)域運行時數(shù)據(jù)區(qū)域 JVM 執(zhí)行 Java 程序的過程中 會使用到各種數(shù)據(jù)區(qū)域 這些區(qū)域有各自的用途 創(chuàng)建和銷毀時間 根據(jù) Java 虛擬機規(guī)范 第二版 下文稱 VM Spec 的規(guī)定 JVM 包括下列幾個運行時數(shù)據(jù)區(qū)域 1 程序計數(shù)器 Program Counter Register 每一個 Java 線程都有一個程序計數(shù)器來用于保存程序執(zhí)行到當(dāng)前方法的哪一個指令 對于非 Native 方法 這個區(qū)域記錄的是正在執(zhí)行的 VM 原語的地址 如果正在執(zhí)行的是 Natvie 方法 這個區(qū)域則為空 undefined 此內(nèi)存區(qū)域是唯一一個在 VM Spec 中沒有規(guī)定任何 OutOfMemoryError 情況的區(qū)域 2 Java 虛擬機棧 Java Virtual Machine Stacks 與程序計數(shù)器一樣 VM 棧的生命周期也是與線程相同 VM 棧描述的是 Java 方法調(diào)用的內(nèi)存模型 每個 方法被執(zhí)行的時候 都會同時創(chuàng)建一個幀 Frame 用于存儲本地變量表 操作棧 動態(tài)鏈接 方法出入 口等信息 每一個方法的調(diào)用至完成 就意味著一個幀在 VM 棧中的入棧至出棧的過程 在后文中 我們 將著重討論 VM 棧中本地變量表部分 經(jīng)常有人把 Java 內(nèi)存簡單的區(qū)分為堆內(nèi)存 Heap 和棧內(nèi)存 Stack 實際中的區(qū)域遠比這種觀點復(fù) 雜 這樣劃分只是說明與變量定義密切相關(guān)的內(nèi)存區(qū)域是這兩塊 其中所指的 堆 后面會專門描述 而所 指的 棧 就是 VM 棧中各個幀的本地變量表部分 本地變量表存放了編譯期可知的各種標(biāo)量類型 boolean byte char short int float long double 對象引用 不是對象本身 僅僅是一個引 用指針 方法返回地址等 其中 long 和 double 會占用 2 個本地變量空間 32bit 其余占用 1 個 本 地變量表在進入方法時進行分配 當(dāng)進入一個方法時 這個方法需要在幀中分配多大的本地變量是一件完 全確定的事情 在方法運行期間不改變本地變量表的大小 在 VM Spec 中對這個區(qū)域規(guī)定了 2 中異常狀況 如果線程請求的棧深度大于虛擬機所允許的深度 將拋 出 StackOverflowError 異常 如果 VM ??梢詣討B(tài)擴展 VM Spec 中允許固定長度的 VM 棧 當(dāng)擴展 時無法申請到足夠內(nèi)存則拋出 OutOfMemoryError 異常 3 本地方法棧 Native Method Stacks 本地方法棧與 VM 棧所發(fā)揮作用是類似的 只不過 VM 棧為虛擬機運行 VM 原語服務(wù) 而本地方法棧是為 虛擬機使用到的 Native 方法服務(wù) 它的實現(xiàn)的語言 方式與結(jié)構(gòu)并沒有強制規(guī)定 甚至有的虛擬機 譬如 Sun Hotspot 虛擬機 直接就把本地方法棧和 VM 棧合二為一 和 VM 棧一樣 這個區(qū)域也會拋出 StackOverflowError 和 OutOfMemoryError 異常 4 Java 堆 Java Heap 對于絕大多數(shù)應(yīng)用來說 Java 堆是虛擬機管理最大的一塊內(nèi)存 Java 堆是被所有線程共享的 在虛擬機 啟動時創(chuàng)建 Java 堆的唯一目的就是存放對象實例 絕大部分的對象實例都在這里分配 這一點在 VM Spec 中的描述是 所有的實例以及數(shù)組都在堆上分配 原文 The heap is the runtime data area from which memory for all class instances and arrays is allocated 但是在逃逸分析和標(biāo)量替換優(yōu)化技術(shù)出 現(xiàn)后 VM Spec 的描述就顯得并不那么準確了 Java 堆內(nèi)還有更細致的劃分 新生代 老年代 再細致一點的 eden from survivor to survivor 甚至 更細粒度的本地線程分配緩沖 TLAB 等 無論對 Java 堆如何劃分 目的都是為了更好的回收內(nèi)存 或 者更快的分配內(nèi)存 在本章中我們僅僅針對內(nèi)存區(qū)域的作用進行討論 Java 堆中的上述各個區(qū)域的細節(jié) 可參見本文第二章 JVM 內(nèi)存管理 深入垃圾收集器與內(nèi)存分配策略 根據(jù) VM Spec 的要求 Java 堆可以處于物理上不連續(xù)的內(nèi)存空間 它邏輯上是連續(xù)的即可 就像我們的 磁盤空間一樣 實現(xiàn)時可以選擇實現(xiàn)成固定大小的 也可以是可擴展的 不過當(dāng)前所有商業(yè)的虛擬機都是 按照可擴展來實現(xiàn)的 通過 Xmx 和 Xms 控制 如果在堆中無法分配內(nèi)存 并且堆也無法再擴展時 將 會拋出 OutOfMemoryError 異常 5 方法區(qū) Method Area 叫 方法區(qū) 可能認識它的人還不太多 如果叫永久代 Permanent Generation 它的粉絲也許就多了 它 還有個別名叫做 Non Heap 非堆 但是 VM Spec 上則描述方法區(qū)為堆的一個邏輯部分 原文 the method area is logically part of the heap 這個名字的問題還真容易令人產(chǎn)生誤解 我們在這里就不糾 結(jié)了 方法區(qū)中存放了每個 Class 的結(jié)構(gòu)信息 包括常量池 字段描述 方法描述等等 VM Space 描述中對這 個區(qū)域的限制非常寬松 除了和 Java 堆一樣不需要連續(xù)的內(nèi)存 也可以選擇固定大小或者可擴展外 甚 至可以選擇不實現(xiàn)垃圾收集 相對來說 垃圾收集行為在這個區(qū)域是相對比較少發(fā)生的 但并不是某些描 述那樣永久代不會發(fā)生 GC 至少對當(dāng)前主流的商業(yè) JVM 實現(xiàn)來說是如此 這里的 GC 主要是對常量池 的回收和對類的卸載 雖然回收的 成績 一般也比較差強人意 尤其是類卸載 條件相當(dāng)苛刻 6 運行時常量池 Runtime Constant Pool Class 文件中除了有類的版本 字段 方法 接口等描述等信息外 還有一項信息是常量表 constant pool table 用于存放編譯期已可知的常量 這部分內(nèi)容將在類加載后進入方法區(qū) 永久代 存放 但是 Java 語言并不要求常量一定只有編譯期預(yù)置入 Class 的常量表的內(nèi)容才能進入方法區(qū)常量池 運行期間也可將 新內(nèi)容放入常量池 最典型的 String intern 方法 運行時常量池是方法區(qū)的一部分 自然受到方法區(qū)內(nèi)存的限制 當(dāng)常量池?zé)o法在申請到內(nèi)存時會拋出 OutOfMemoryError 異常 7 本機直接內(nèi)存 Direct Memory 直接內(nèi)存并不是虛擬機運行時數(shù)據(jù)區(qū)的一部分 它根本就是本機內(nèi)存而不是 VM 直接管理的區(qū)域 但是這 部分內(nèi)存也會導(dǎo)致 OutOfMemoryError 異常出現(xiàn) 因此我們放到這里一起描述 在 JDK1 4 中新加入了 NIO 類 引入一種基于渠道與緩沖區(qū)的 I O 方式 它可以通過本機 Native 函數(shù)庫直 接分配本機內(nèi)存 然后通過一個存儲在 Java 堆里面的 DirectByteBuffer 對象作為這塊內(nèi)存的引用進行操 作 這樣能在一些場景中顯著提高性能 因為避免了在 Java 對和本機堆中來回復(fù)制數(shù)據(jù) 顯然本機直接內(nèi)存的分配不會受到 Java 堆大小的限制 但是即然是內(nèi)存那肯定還是要受到本機物理內(nèi)存 包括 SWAP 區(qū)或者 Windows 虛擬內(nèi)存 的限制的 一般服務(wù)器管理員配置 JVM 參數(shù)時 會根據(jù)實際 內(nèi)存設(shè)置 Xmx 等參數(shù)信息 但經(jīng)常忽略掉直接內(nèi)存 使得各個內(nèi)存區(qū)域總和大于物理內(nèi)存限制 包括物 理的和操作系統(tǒng)級的限制 而導(dǎo)致動態(tài)擴展時出現(xiàn) OutOfMemoryError 異常 實戰(zhàn)實戰(zhàn) OutOfMemoryError 上述區(qū)域中 除了程序計數(shù)器 其他在 VM Spec 中都描述了產(chǎn)生 OutOfMemoryError 下稱 OOM 的情 形 那我們就實戰(zhàn)模擬一下 通過幾段簡單的代碼 令對應(yīng)的區(qū)域產(chǎn)生 OOM 異常以便加深認識 同時初步介 紹一些與內(nèi)存相關(guān)的虛擬機參數(shù) 下文的代碼都是基于 Sun Hotspot 虛擬機 1 6 版的實現(xiàn) 對于不同公司的不 同版本的虛擬機 參數(shù)與程序運行結(jié)果可能結(jié)果會有所差別 Java 堆堆 Java 堆存放的是對象實例 因此只要不斷建立對象 并且保證 GC Roots 到對象之間有可達路徑即可產(chǎn)生 OOM 異常 測試中限制 Java 堆大小為 20M 不可擴展 通過參數(shù) XX HeapDumpOnOutOfMemoryError 讓 虛擬機在出現(xiàn) OOM 異常的時候 Dump 出內(nèi)存映像以便分析 關(guān)于 Dump 映像文件分析方面的內(nèi)容 可參見 本文第三章 JVM 內(nèi)存管理 深入 JVM 內(nèi)存異常分析與調(diào)優(yōu) 清單 1 Java 堆 OOM 測試 VM Args Xms20m Xmx20m XX HeapDumpOnOutOfMemoryError author zzm public class HeapOOM static class OOMObject public static void main String args List list new ArrayList while true list add new OOMObject 運行結(jié)果 java lang OutOfMemoryError Java heap space Dumping heap to java pid3404 hprof Heap dump file created 22045981 bytes in 0 663 secs VM 棧和本地方法棧棧和本地方法棧 Hotspot 虛擬機并不區(qū)分 VM 棧和本地方法棧 因此 Xoss 參數(shù)實際上是無效的 棧容量只由 Xss 參數(shù)設(shè) 定 關(guān)于 VM 棧和本地方法棧在 VM Spec 描述了兩種異常 StackOverflowError 與 OutOfMemoryError 當(dāng)棧 空間無法繼續(xù)分配分配時 到底是內(nèi)存太小還是棧太大其實某種意義上是對同一件事情的兩種描述而已 在筆 者的實驗中 對于單線程應(yīng)用嘗試下面 3 種方法均無法讓虛擬機產(chǎn)生 OOM 全部嘗試結(jié)果都是獲得 SOF 異常 1 使用 Xss 參數(shù)削減棧內(nèi)存容量 結(jié)果 拋出 SOF 異常時的堆棧深度相應(yīng)縮小 2 定義大量的本地變量 增大此方法對應(yīng)幀的長度 結(jié)果 拋出 SOF 異常時的堆棧深度相應(yīng)縮小 3 創(chuàng)建幾個定義很多本地變量的復(fù)雜對象 打開逃逸分析和標(biāo)量替換選項 使得 JIT 編譯器允許對象拆分 后在棧中分配 結(jié)果 實際效果同第二點 清單 2 VM 棧和本地方法棧 OOM 測試 僅作為第 1 點測試程序 VM Args Xss128k author zzm public class JavaVMStackSOF private int stackLength 1 public void stackLeak stackLength stackLeak public static void main String args throws Throwable JavaVMStackSOF oom new JavaVMStackSOF try oom stackLeak catch Throwable e System out println stack length oom stackLength throw e 運行結(jié)果 stack length 2402 Exception in thread main java lang StackOverflowError at org fenixsoft oom JavaVMStackSOF stackLeak JavaVMStackSOF java 20 at org fenixsoft oom JavaVMStackSOF stackLeak JavaVMStackSOF java 21 at org fenixsoft oom JavaVMStackSOF stackLeak JavaVMStackSOF java 21 如果在多線程環(huán)境下 不斷建立線程倒是可以產(chǎn)生 OOM 異常 但是基本上這個異常和 VM ??臻g夠不夠 關(guān)系沒有直接關(guān)系 甚至是給每個線程的 VM 棧分配的內(nèi)存越多反而越容易產(chǎn)生這個 OOM 異常 原因其實很好理解 操作系統(tǒng)分配給每個進程的內(nèi)存是有限制的 譬如 32 位 Windows 限制為 2G Java 堆和方法區(qū)的大小 JVM 有參數(shù)可以限制最大值 那剩余的內(nèi)存為 2G 操作系統(tǒng)限制 Xmx 最大堆 MaxPermSize 最大方法區(qū) 程序計數(shù)器消耗內(nèi)存很小 可以忽略掉 那虛擬機進程本身耗費的內(nèi)存不計算 的話 剩下的內(nèi)存就供每一個線程的 VM 棧和本地方法棧瓜分了 那自然每個線程中 VM 棧分配內(nèi)存越多 就 越容易把剩下的內(nèi)存耗盡 清單 3 創(chuàng)建線程導(dǎo)致 OOM 異常 VM Args Xss2M 這時候不妨設(shè)大些 author zzm public class JavaVMStackOOM private void dontStop while true public void stackLeakByThread while true Thread thread new Thread new Runnable Override public void run dontStop thread start public static void main String args throws Throwable JavaVMStackOOM oom new JavaVMStackOOM oom stackLeakByThread 特別提示一下 如果讀者要運行上面這段代碼 記得要存盤當(dāng)前工作 上述代碼執(zhí)行時有很大令操作系統(tǒng) 卡死的風(fēng)險 運行結(jié)果 Exception in thread main java lang OutOfMemoryError unable to create new native thread 運行時常量池運行時常量池 要在常量池里添加內(nèi)容 最簡單的就是使用 String intern 這個 Native 方法 由于常量池分配在方法區(qū)內(nèi) 我們只需要通過 XX PermSize 和 XX MaxPermSize 限制方法區(qū)大小即可限制常量池容量 實現(xiàn)代碼如下 清單 4 運行時常量池導(dǎo)致的 OOM 異常 VM Args XX PermSize 10M XX MaxPermSize 10M author zzm public class RuntimeConstantPoolOOM public static void main String args 使用 List 保持著常量池引用 壓制 Full GC 回收常量池行 為 List list new ArrayList 10M 的 PermSize 在 integer 范圍內(nèi)足夠產(chǎn)生 OOM 了 int i 0 while true list add String valueOf i intern 運行結(jié)果 Exception in thread main java lang OutOfMemoryError PermGen space at java lang String intern Native Method at org fenixsoft oom RuntimeConstantPoolOOM main RuntimeConstantPoolOOM java 18 方法區(qū)方法區(qū) 上文講過 方法區(qū)用于存放 Class 相關(guān)信息 所以這個區(qū)域的測試我們借助 CGLib 直接操作字節(jié)碼動態(tài)生 成大量的 Class 值得注意的是 這里我們這個例子中模擬的場景其實經(jīng)常會在實際應(yīng)用中出現(xiàn) 當(dāng)前很多主流 框架 如 Spring Hibernate 對類進行增強時 都會使用到 CGLib 這類字節(jié)碼技術(shù) 當(dāng)增強的類越多 就需要 越大的方法區(qū)用于保證動態(tài)生成的 Class 可以加載入內(nèi)存 清單 5 借助 CGLib 使得方法區(qū)出現(xiàn) OOM 異常 VM Args XX PermSize 10M XX MaxPermSize 10M author zzm public class JavaMethodAreaOOM public static void main String args while true Enhancer enhancer new Enhancer enhancer setSuperclass OOMObject class enhancer setUseCache false enhancer setCallback new MethodInterceptor public Object intercept Object obj Method method Object args MethodProxy proxy throws Throwable return proxy invokeSuper obj args enhancer create static class OOMObject 運行結(jié)果 Caused by java lang OutOfMemoryError PermGen space at java lang ClassLoader defineClass1 Native Method at java lang ClassLoader defineClassCond ClassLoader java 632 at java lang ClassLoader defineClass ClassLoader java 616 8 more 本機直接內(nèi)存本機直接內(nèi)存 DirectMemory 容量可通過 XX MaxDirectMemorySize 指定 不指定的話默認與 Java 堆 Xmx 指定 一 樣 下文代碼越過了 DirectByteBuffer 直接通

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論