深入理解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()方法。
相關推薦
深入理解JVM:OutOfMemory實戰
除了程式計數器外,虛擬機器記憶體的其他幾個執行時區域都有發生OutOfMemoryError(OOM),下面我們來詳細分析。Java堆溢位 Java堆用於儲存物件例項,只要不斷的建立物件,並且保證GC Roots到物件之間有可達路徑來避免垃圾回收機制來清除這些物件,那麼物
深入理解JVM:HotSpot虛擬機對象探秘
意願 對象分配 初始化 處理 失敗 每一個 面向 this 線程id 對象的創建 java是一門面向對象的語言。在Java程序執行過程中無時無刻有Java對象被創建出來。在語言層面上,創建對象(克隆、反序列化)一般是一個newkeyword而已,而在虛
深入理解JVM:JVM執行時數據區域分類
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虛擬機器1:JVM記憶體的結構與永久代的消失
所有的Java開發人員可能會遇到這樣的困惑?我該為堆記憶體設定多大空間呢?OutOfMemoryError的異常到底涉及到執行時資料的哪塊區域?該怎麼解決呢?其實如果你經常解決伺服器效能問題,那麼這些問題就會變的非常常見,瞭解JVM記憶體也是為了伺服器出現效能問題的時候可