JVM內存區域與內存溢出異常

Java虛擬機在執行java程序時會把它所管理的內存會分爲若干個不一樣的數據區域,不一樣的區域在內存不足時會拋出不一樣的異常。html

1、運行時數據區域的劃分

(1)程序計數器
程序計數器(Program Counter Register)是一塊比較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器;
PCR爲線程私有內存,程序計數器是惟一一個在Java虛擬機規範中沒有規定任何OOM狀況的區域。java

(2)方法區
方法區(Method Area)與Java堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
也稱爲永久代(Permanent Generation)但隨着Java8的到來,已放棄永久代改成採用Native Memory來實現方法區的規劃。
此區域回收目標主要是針對常量池的回收和對類型的卸載。
git

(3)JVM棧
虛擬機棧(Java Virtual Machine Stacks)描述的是Java方法執行的內存模型:每一個方法在在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法接口等信息。每一個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧出棧的過程。
Java虛擬機棧也是線程私有,它的生命週期與線程相同。
Java內存區常分爲堆內存(Heap)和棧內存(Stack);github

OOM狀況:
線程請求的棧深度>虛擬機所運行的最大深度;
虛擬機動態擴展時沒法申請到足夠的內存多線程

(4)本地方法棧
與虛擬機棧做用很類似,區別是虛擬機棧爲虛擬機執行java方法服務,而本地方法棧則是爲虛擬機用到的Native方法服務。和虛擬機棧同樣可能拋出StackOverflowError和OutOfMemoryError異常。框架

(5)Java堆
Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。主要存放對象實例。
Java堆是垃圾收集器管理的主要區域,其可細分爲新生代和老年代。若是在堆中沒有內存完成實例分配,而且也沒法再擴展時,會拋出OutOfMemoryError異常。工具

(6)直接內存
直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域。
能在一些場景中顯著提升性能,由於避免了在Java堆和Native堆中來回複製數據。
直接內存的分配不會受到Java堆大小的限制,但會收到本機總內存(RAM以及SWAP/分頁文件)大小以及處理器尋址空間的限制。
設置Xmx等參數信息時注意不能忽略直接內存,否則會引發OOM。佈局

2、對象的訪問定位

Java程序須要經過棧上的reference數據來操做堆上的具體對象,
對象訪問方法取決於不一樣的JVM實現,目前主流訪問方式有使用句柄和直接指針2種。性能

(1)句柄訪問學習

Java堆中劃分出一塊內存做爲句柄池,reference中存儲對象的句柄地址,句柄中包含對象實例數據與類型數據各自的具體地址信息;

 

(2)直接指針訪問

Java堆對象的佈局中必須考慮如何放置訪問類型數據的相關信息,reference中存儲對象地址;

 

(3)兩種訪問方式比較
使用句柄訪問最大的好處是reference中存儲的是穩定的句柄地址,在對象被移動(GC時移動對象是很廣泛的行爲)時只會改變句柄中的實例數據指針,而reference自己不須要修改;
使用直接指針訪問方式的最大好處是速度更快,它節省了一次指針定位的時間開銷,因爲對象訪問在Java中很是頻繁,所以這類開銷聚沙成塔後也是一項很是可觀的執行成本;
HotSpot虛擬機採用指針訪問方式進行對象訪問,從整個軟件開發範圍看,各類語言和框架使用句柄來訪問的狀況也很是常見。

3、幾種內存溢出異常及解決

虛擬機主要的幾種異常是OutOfMemoryError異常和虛擬機棧和本地方法棧溢出,以及運行時常量池溢出等。

(1)OutOfMemoryError異常

除了程序計數器外,虛擬機內存的其餘幾個運行時區域都有發生OutOfMemoryError(OOM)異常的可能,

java堆用於存儲對象實例,咱們只要不斷的建立對象,而且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,就會在對象數量達到最大堆容量限制後產生內存溢出異常。
出現這種異常,通常手段是先經過內存映像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉存快照進行分析,重點是確認內存中的對象是不是必要的,先分清是由於內存泄漏(Memory Leak)仍是內存溢出(Memory Overflow)。
若是是內存泄漏,可進一步經過工具查看泄漏對象到GC Roots的引用鏈。因而就能找到泄漏對象時經過怎樣的路徑與GC Roots相關聯並致使垃圾收集器沒法自動回收。
若是不存在泄漏,那就應該檢查虛擬機的參數(-Xmx與-Xms)的設置是否適當。

(2)虛擬機棧和本地方法棧溢出
若是線程請求的棧深度大於虛擬機所容許的最大深度,將拋出StackOverflowError異常。
若是虛擬機在擴展棧時沒法申請到足夠的內存空間,則拋出OutOfMemoryError異常
這裏須要注意每一個線程分配到的棧容量越大,能夠創建的線程數就越少,創建線程時候就越容易耗盡剩餘內存。

按虛擬機默認參數,棧深度在大多數狀況下達到1000~2000徹底沒問題,對於正常方法調用(包括遞歸),這個深度應該徹底夠用;

但若是是創建過多線程致使內存溢出,在不能減小線程數或者更換X64位虛擬機的狀況下,就只能經過減小最大堆和減小棧容量來換取更多的線程

(3)方法區和運行時常量區溢出
java.lang.OutOfMemoryError: PermGen space
運行時常量池溢出(HotSpot虛擬機中的永久代),
若是要向運行時常量池中添加內容,最簡單的作法就是使用String.intern()這個Native方法。

運行時常量池溢出(HotSpot虛擬機中的永久代),
若是要向運行時常量池中添加內容,最簡單的作法就是使用String.intern()這個Native方法。
該方法的做用是若是池中已經包含一個等於此String的字符串,則返回表明池中這個字符串的String對象;不然,將此String對象包含的字符串添加到常量池中,而且返回此String對象的引用。
因爲常量池分配在方法區內,咱們能夠經過-XX:PermSize和-XX:MaxPermSize限制方法區的大小,從而間接限制其中常量池的容量。

 

4、Java如何得到JVM內存使用狀況 

要得到JVM相關的內存信息,須要使用Runtime類的totalMemory(), maxMemory() 和 freeMemory()三個方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.text.DecimalFormat;
 
public class Test{
 
/**
* 顯示JVM總內存,JVM最大內存和總空閒內存
*/
public void displayAvailableMemory() {
 
DecimalFormat df = new DecimalFormat(「 0.00 ″) ;
 
//顯示JVM總內存
long totalMem = Runtime.getRuntime().totalMemory();
 
System.out.println(df.format(totalMem 1000000F) + 」 MB」);
 
//顯示JVM嘗試使用的最大內存
long maxMem = Runtime.getRuntime().maxMemory();
System.out.println(df.format(maxMem 1000000F) + 」 MB」);
 
//空閒內存
long freeMem = Runtime.getRuntime().freeMemory();
System.out.println(df.format(freeMem 1000000F) + 」 MB」);
 
}
 
/**
* Starts the program
* @param args the command line arguments
*/
public static void main(String[] args) {
new Main().displayAvailableMemory();
}
}

  

參考

JVM學習筆記(二)Java內存區域與內存溢出異常

Java 內存區域與內存溢出

 

轉:http://www.cnblogs.com/binyue/p/3927105.html

相關文章
相關標籤/搜索