1. 程式人生 > >深入理解JVM:OutOfMemory實戰

深入理解JVM:OutOfMemory實戰

除了程式計數器外,虛擬機器記憶體的其他幾個執行時區域都有發生OutOfMemoryError(OOM),下面我們來詳細分析。
Java堆溢位
Java堆用於儲存物件例項,只要不斷的建立物件,並且保證GC Roots到物件之間有可達路徑來避免垃圾回收機制來清除這些物件,那麼物件數量到達最大堆容量限制後就會產生記憶體溢位異常。例如:

?
1 2 3 4 5 6 7 8 9 <code class="hljs cs">// VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError public class HeapOOM{ public
static void main(String[] args){ List<oomobject> list = new ArrayList<oomobject>(); while(true){ list.add(new OOMObject()); } } }</oomobject></oomobject></code>

Java堆的大小限制為20M,不可擴充套件(將堆的最小值-Xms引數與最大值-Xmx最大值引數設定為一樣,避免自動擴充套件)通過引數-XX:+HeapDumpOnOutOfMemoryError,可以讓虛擬機器在出現記憶體溢位時Dump出當前的記憶體轉儲快照以便事後進行分析。
要解決這個區域的異常,一般的手段是先通過記憶體映像工具如(Eclipse MemoryAnalyzer)對Dump出來的堆轉儲快照進行分析,重點是確認記憶體中的物件是否是必要的,也就是要先分析到底是出現了記憶體洩露(Memory Leak)還是記憶體溢位(Memory Overflow)
如果是記憶體洩露,可進一步通過工具檢視洩露物件到GC Roots的引用鏈,這樣就比較容易確定發生洩露的程式碼位置。
如果不存在記憶體洩露,那就應當檢查虛擬機器堆引數(-Xmx與-Xms),與機器實體記憶體對比看是否還可以調大,從程式碼上檢查是否存在某些物件生命週期過長、持有狀態時間過長的情況,嘗試減少程式執行期的記憶體消耗。
虛擬機器棧和本地方法棧溢位


在Hotspot虛擬機器中並不區分虛擬機器棧和本地方法棧,以此,對於Hotspot來說,雖然-Xoss引數(設定本地方法棧大小)存在,但實際上是無效的,棧容量只由-Xss引數設定,關於虛擬機器棧和本地方法棧可以出現以下兩週異常:
1、如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,將丟擲StackOverflowError異常
2、如果虛擬機器在擴充套件時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常
舉個例子:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <code class="hljs cs"><code
class="hljs cs">public class JavaVMStackSOF{ private int stackLength = 1; public void stackLeak(){ stackLength ++; stackLeak(); } public static void main(String[] args){ JavaVMStackSOF oom = new JavaVMStackSOF(); try{ oom.stackLeak(); }catch(Throwable e){ throw e; } } }</code></code>

將此程式碼執行在單執行緒下,均讓無法讓虛擬機器產生OutOfMemoryError異常,嘗試結果都是StackOverflowError異常。
1、使用-Xss 引數減少棧記憶體容量,結果:丟擲StackOverflowError異常,異常出現時輸出的棧的深度相應縮小
2、定義了大量的本地變數,增大此方法幀中本地變量表的長度。結果:丟擲StackOverflowError異常時輸出的堆疊深度相應減小。
因此,在單執行緒下,無論是由於棧幀太大還是虛擬機器容量太小,當記憶體無法分配的時候虛擬機器都丟擲的是StackOverflowError。
如果測試不限於單執行緒,通過不斷的建立執行緒的方式倒是可以產生內除溢位異常,但是這樣產生的記憶體溢位與棧空間是否夠大不存在任何聯絡,或者說,為每個執行緒的棧分配的記憶體越大,然而越容易產生記憶體溢位異常。
原因是,作業系統分配給每個執行緒的記憶體是有限的,32位window為2G,虛擬機器提供了引數來控制Java堆和方法區的這兩部分記憶體的最大值。剩餘的記憶體為2G(作業系統記憶體)減去Xmx(堆最大容量),再減去MaxPermSize(最大方法區容量),程式計數器消耗的記憶體很小,可以忽略。如果虛擬機器程序本身耗費的記憶體不計算在內,剩下的記憶體就由虛擬機器棧和本地方法棧瓜分了。每個執行緒分配到棧容量越大,可以建立的執行緒數自然越少,建立執行緒時越容易把剩餘的記憶體耗盡。
方法區和執行時常量池溢位
執行時常量池是方法區的一部反,這兩個可以放在一起。
String.intern()方法是一個native方法,他的作用是:如果字串常量池中已經包含一個等於此String物件的字串,則返回代表池中這個字串的string物件;否則,將此String物件包含的字串新增到常量池中,並返回此String物件的引用。
我們可以通過-XX:PermSize和-XX:MaxPermSize限制方法區大小,從而間接限制其中常量池的容量,程式碼如下:

?
1 2 3 4 5 6 7 8 9 10 <code class="hljs cs"><code class="hljs cs"><code class="hljs cs">public class RuntimeConstantPoolOOM{ public static void main(String[] args){ // 使用List保持著常量池引用,避免Full GC 回收常量池行為 List<string> list = new ArrayList<string>(); int i=0; while(true){ list.add(String.valueOf(i++).intern()); } } }</string></string></code></code></code>

方法區用於存放Class相關資訊,如類名、訪問修飾符、常量池、欄位描述、方法描述等。方法區的溢位是一個類要被垃圾收集器回收掉,判斷條件是比較苛刻的。在經常動態生成大量Class應用中,需要特別注意類的回收情況。場景有程式使用了CGLib位元組碼增強和動態語言,還有大量jsp或動態產生jsp檔案的應用。例如:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <code class="hljs cs"><code class="hljs cs"><code class="hljs cs"><code class="hljs cs">public class JavaMethodAreaOOM{ public static void main(String[] args){ while(true){ Enhancer e = new Enhancer(); e.setSuperClass(OOMObject.class); e.setUseCache(false); e.setCallback(new MethodInterceptor(){ public Object interceptor(Object obj,Method method,Object[] args,MethodProxy proxy){ return proxy.invokeSuper(obj,args); } }); e.create(); } } }</code></code></code></code>

本機直接記憶體溢位
DirectMemory容量可通過-XX:MaxDirectMemorySize指定,如果不指定,則預設與Java堆最大值-Xmx一樣,例如:

?
1 2 3 4 5 6 7 8 9 10 11 <code class="hljs cs"><code class="hljs cs"><code class="hljs cs"><code class="hljs cs"><code class="hljs cs">public class DirectMemoryOOM{ private static final _1MB = 1024*1024; public static void main(String[] args){ Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe)unsafeField.get(null); while(true){ unsafe.allocateMemory(_1MB); } } }</code></code></code></code></code>

以上程式碼直接通過反射獲取Unsafe例項進行記憶體分配。雖然使用DirectByteBuffer分配記憶體也會丟擲記憶體溢位異常,但是他丟擲異常時並沒有真正向操作系統申請分配記憶體,而是通過計算得知記憶體無法分配,於是收到丟擲異常,真正申請分配記憶體的方法時unsafe.allocateMemory()方法。

相關推薦

深入理解JVMOutOfMemory實戰

除了程式計數器外,虛擬機器記憶體的其他幾個執行時區域都有發生OutOfMemoryError(OOM),下面我們來詳細分析。Java堆溢位 Java堆用於儲存物件例項,只要不斷的建立物件,並且保證GC Roots到物件之間有可達路徑來避免垃圾回收機制來清除這些物件,那麼物

深入理解JVMHotSpot虛擬機對象探秘

意願 對象分配 初始化 處理 失敗 每一個 面向 this 線程id 對象的創建 java是一門面向對象的語言。在Java程序執行過程中無時無刻有Java對象被創建出來。在語言層面上,創建對象(克隆、反序列化)一般是一個newkeyword而已,而在虛

深入理解JVMJVM執行時數據區域分類

return 位置 工作 () 對象 地方法 存在 utm 連續 JVM在運行java程序的過程中會把他所管理的內存劃分為若幹個不同的數據區域。這些區域都有各自的用途和創建、銷毀時間。有些區域隨著虛擬機的啟動而存在。有些區域則依賴用戶線程的啟動和結束而建

深入理解JVM垃圾收集器與內存分配策略

四種 內存回收 第一次 不可達 append test 方法 static hot 堆裏面存放著Java世界差點兒全部的對象實例,垃圾收集器在對堆進行回收前。第一件事情就是要確定這些對象之中哪些還存活,哪些已經死去。推斷對象的生命周期是否結束有下面幾種方

筆記深入理解JVM 第5章 調優案例分析與實戰

1、每天15萬 PV 的線上文件型別網站 環境:4 CPU,16GB 記憶體, 64位 CentOS 5.4 問題:網站失去響應 原先JVM配置:JDK1.5,  -Xmx12G  -Xms12G 解決過程:發現問題來自GC停頓(12G記憶體 的 Full GC 需要12秒

深入理解JVM虛擬機(一)Java運行時數據區域

字面量 符號 地方 64位 因此 lower 優化 java堆大小 工作 概述 JVM是Java語言的精髓所在,因為它Java語言實現了跨平臺運行,以及自動內存管理機制等,本文將從概念上介紹JVM內存的各個區域,說明個區域的作用。 JVM運行時數據區模型 Java虛擬機在執

深入理解JVM(一)執行時資料區

深入理解JVM(一):執行時資料區 執行時資料區 JVM在執行java程式的過程中,會把記憶體分為幾個不同的資料區域,如上圖所示。 程式計數器 雖然圖片中程式計數器所佔的面積比較大,但實際上程式計數器所佔的記憶體非常小,也是唯一一塊在所有JVM中都沒有規定OOM的區

深入理解JVM垃圾收集演算法

垃圾收集演算法主要有以下幾種:標記-清除演算法(mark-sweep)、複製演算法(copying)和標記-整理演算法(mark-compact)。 標記-清除演算法: 演算法的執行過程與名字一樣,先標記所有需要回收的物件,在標記完成後統一回收所有被標記的物件。該演算法有兩個問題: 標記和清

深入理解JVM虛擬機器讀書筆記【第九章】類載入及執行子系統的案例與實戰

9.1 概述 9.2 案例分析 9.2.1 Tomcat:正統的類載入器架構 9.2.2 OSGI:靈活的類載入器架構 9.2.3 位元組碼生成技術與動態代理

深入理解JVM虛擬機器(五)位元組碼指令簡介

Java 虛擬機器的指令由一個位元組長度的、代表著某種特定操作含義的數字(稱為操作碼)以及跟隨其後的零至多個代表此操作所需引數(運算元)而構成。由於 Java 虛擬機器採用面向運算元棧而不是暫存器的架構,所以大多數的指令都不包含運算元,只有一個操作碼。 1. 位元組碼與資料型別

深入理解JVM虛擬機器(四)Class類檔案結構(二)

屬性表在前面的講解中出現多次,在Class檔案、欄位表、方法表都可以攜帶自己的屬性表集合,用於描敘某些場景專有的資訊。為了正確解析Class檔案,《Java虛擬機器規範(第二版)》中預定義了9項虛擬機器實現應當識別的屬性。然而在最新的《Java虛擬機器規範(Java SE7)》中屬性表已經增

深入理解JVM虛擬機器(三)虛擬機器效能監控工具

本部落格將講解Java虛擬機器效能監控工具的使用以及對Java虛擬機器進行效能監控的實驗。Java開發人員需要對虛擬機器效能監控工具的使用進行掌握,這是很有必要的。 1.概述 給一個系統定位問題的時候,知識、經驗是關鍵基礎,資料是依據。工具是運用知識處理資料的手段。這裡說的資料包括:

深入理解JVM虛擬機器(二)垃圾回收機制

談起GC,應該是讓Java程式設計師最激動的一項技術,我相信每個Java程式設計師都有探究GC本質的衝動!JVM垃圾回收機制對於瞭解物件的建立和物件的回收極為重要,是每個Java程式設計師必須掌握的技能。 本部落格圍繞三個問題來展開 哪些記憶體需要回收? 什

深入理解JVM讀書筆記二虛擬機器類載入機制

一、概述      虛擬機器把描述類的資料從class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化。最終形成可以被虛擬機器最直接使用的java型別的過程就是虛擬機器的類載入機制。      與那些在編譯時需要進行連線工作的語

深入理解JVM讀書筆記二垃圾收集器與記憶體分配策略

一、判斷物件死亡的兩種常用演算法:                在堆裡面存放著java世界中幾乎所有的例項物件,垃圾收集器在堆進行回收前,第一件事情就是要確定哪些物件還存活著,哪些已經死去。 1、引

深入理解JVM(六)虛擬機器類載入機制

虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。 在Java中,型別的載入、連線和初始化過程都是程式在執行期間完成的,這種策略雖然會令類載入時稍微增

深入理解JVM虛擬機器(九)執行期優化與JIT編譯器

1. JIT編譯器的引入 首先我們這篇文章中所說的編譯器都是指JVM的組成部分之一—即時編譯器(JIT),與生成Java位元組碼的javac編譯器要區分開來。首先我們這篇文章中所說的編譯器都是指JVM的組成部分之一—即時編譯器(JIT),與生成Java位元組碼的javac編譯器要區分開來

深入理解JVM虛擬機器(八)編譯器優化

本部落格從編譯期原始碼實現的層次上讓我們瞭解了Java原始碼編譯為位元組碼的過程,分析了Java語言中泛型、主動裝箱/拆箱、條件編譯等多種語法糖的前因後果。 1. 概述 java語言的“編譯期”其實是一段“不確定”的操作過程,因為它可能是指一個前端編譯器(其實叫“編譯器的前端”更準確

深入理解JVM虛擬機器(七)虛擬機器位元組碼執行引擎

程式碼編譯的結果就是從本地機器碼轉變為位元組碼。我們都知道,編譯器將Java原始碼轉換成位元組碼?那麼位元組碼是如何被執行的呢?這就涉及到了JVM位元組碼執行引擎,執行引擎負責具體的程式碼呼叫及執行過程。就目前而言,所有的執行引擎的基本一致: 輸入:位元組碼檔案

深入理解JVM虛擬機器1JVM記憶體的結構與永久代的消失

所有的Java開發人員可能會遇到這樣的困惑?我該為堆記憶體設定多大空間呢?OutOfMemoryError的異常到底涉及到執行時資料的哪塊區域?該怎麼解決呢?其實如果你經常解決伺服器效能問題,那麼這些問題就會變的非常常見,瞭解JVM記憶體也是為了伺服器出現效能問題的時候可