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ù)免費(fèi)閱讀

下載本文檔

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

文檔簡介

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

溫馨提示

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

評(píng)論

0/150

提交評(píng)論