Java---(內存區域 與 內存溢出異常)

運行時數據區域

  • 線程私有區域:程序計數器,Java虛擬機棧,本地方法棧(其生命週期與相關線程有關,隨線程而生,隨線程而滅)
  • 線程共享區域:Java堆,方法區,運行時常量池

程序計數器(線程私有)

  • 由於JVM的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現,因此在任何一個確定的時刻,一個處理器都只會執行一條線程中的指令。爲了切換線程後能恢復到正確的執行位置,每條線程都需要獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲。
  • 程序計數器是一塊較小的內存空間,可以說是當前線程所執行的字節碼的行號指示器;如果當前線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是一個Native方法,這個計數器值爲空
  • 程序計數器內存區域是唯一一個在JVM規範中沒有規定任何OOM情況的區域

Java虛擬機棧(線程私有)

  • 虛擬機棧描述的是Java方法執行的內存模型:每個方法執行的同時都會創建一個棧幀用於存儲局部變量表,操作數棧,動態鏈接,方法出口等信息;每一個方法從調用直至執行完成的過程,就對應一個棧幀在虛擬機棧中出入棧的過程,生命週期與線程相同。
  • 局部變量表:存放編譯器的基本數據類型,對象引用;其所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在執行期間不改變局部變量表的大小
  • 此區域一共會產生以下兩種異常
  • 如果線程請求的棧深度大於虛擬機所允許的深度(-Xss設置棧容量),將會拋出StackOverFlowError異常
  • 虛擬機在動態擴展時無法申請到足夠的內存,會拋出OOM(OutOfMemoryError)異常

本地方法棧(線程私有)

  • 虛擬機使用的Native方法服務
  • 在HotSpot虛擬機中,本地方法棧與虛擬機棧是同一塊內存區域

Java堆(線程共享)

  • Java堆(Java Heap)是JVM所管理的最大內存區域,在JVM啓動時創建,存放對象實例;所有的對象實例以及數組都要在堆上分配
  • Java堆是垃圾回收器管理的主要區域,因此很多時候可以稱之爲"GC堆"。根據JVM規範規定的內容,Java堆可以處於物理上不連續的內存空間中。Java堆在主流的虛擬機中都是可擴展的(-Xmx設置最大值,-Xms設置最小值)。
  • 如果在堆中沒有足夠的內存完成實例分配並且堆也無法再拓展時,將會拋出OOM

方法區(線程共享)

  • 存儲已被虛擬機加載的類信息、常量、靜態變量、及編譯器編譯後的代碼等數據
  • JVM規範規定:當方法區無法滿足內存分配需求時,將拋出OOM異常

運行時常量池(方法區的一部分)

  • 存放字面量(字符串,final常量,基本數據類型的值)與符號引用(類與結構的完全限定名,字段的名稱和描述符,方法的名稱和描述符)

Java堆溢出

  • Java堆用來存儲對象實例,不斷地創建對象,那麼在對象的數量達到最大堆容量後會產生內存溢出異常
public class Test {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list =
new ArrayList<>();
while(true) {
list.add(new OOMObject());
}
}
}
  • 分析問題的產生到底是出現了內存泄露(泄露對象無法被回收)還是內存溢出(內存對象還應該存活)
  • 當發生內存泄露時,應該:
  • 及時關閉流
  • 及時關閉文件,使用時再創建對象
  • 當發生內存溢出時,應該:
  • 內存不夠用,調大內存
  • 正在存活的對象生命週期太長

虛擬機棧和本地方法棧溢出

  • 們HotSpot虛擬機將虛擬機棧與本地方法棧合二爲一,因此對於HotSpot來說,棧容量只需要由-Xss參數來設置
  • 設置JVM參數
  • -Xms:設置堆的最小值
  • -Xmx:設置堆最大值
  • 關於虛擬機棧會產生的兩種異常:
  • 如果線程請求的棧深度大於虛擬機所允許的最大深度,會拋出StackOverFlow異常
  • 如果虛擬機在拓展棧時無法申請到足夠的內存空間,則會拋出OOM異常
觀察StackOverFlow異常(單線程環境下)
JVM參數:-Xss128K


public class Test {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
Test test = new Test();
try {
test.stackLeak();
} catch (Throwable e) {
System.out.println("Stack Length: "+test.stackLength);
throw e;
}
}
}

如果是因爲多線程導致的內存溢出問題,在不能減少線程數的情況下,只能減少最大堆和減少棧容量的方式來換取更多線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章