1. 程式人生 > >深入Java虛擬機器筆記(一):Java記憶體區域與記憶體溢位異常

深入Java虛擬機器筆記(一):Java記憶體區域與記憶體溢位異常

這裡寫圖片描述
1、程式計數器為很小的記憶體空間,為當前執行緒執行的位元組碼的行號指示器,通過改變計數器的值來選取下一條需要執行的位元組碼指令,迴圈、分支等基礎功能都是需要計數器來完成的
2、Java虛擬機器棧為Java方法執行的記憶體模型,每個方法被執行時都會同時建立棧幀用於儲存區域性變量表,操作棧、動態連結、方法出口等資訊,方法被執行到結束對應一個棧幀從虛擬機器棧入棧出棧
兩種異常情況:如果執行緒請求的棧深度大於虛擬機器允許的深度,丟擲StackOverflowError異常,如果虛擬機器棧可以動態擴充套件,只是無法申請到足夠的記憶體時丟擲OutOfMemoryError異常
3、本地方法棧:上面是為Java方法服務,而這個是為虛擬機器使用的Native方法服務
4、Java堆

:被所有執行緒共享的記憶體區域,Java虛擬機器管理的最大記憶體。幾乎所有物件例項存放區域,垃圾收集器管理的主要區域,根據分代收集演算法,Java堆還可以細分:新生代和老年代;Eden空間、From Survivor空間、To Survivor空間;細分主要為了更好地回收記憶體和更快分配記憶體
5、方法區,也是各個執行緒共享的記憶體區域,用於儲存已經被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。不需要連續的記憶體和可以選擇固定大小或者擴充套件,還可以選擇不實現垃圾收集,記憶體回收目標主要針對常量池的回收和對型別的解除安裝
6、執行時常量池:方法區的一部分,用於存放編譯期生成的各種字面量和符號引用,這些內容將在類載入後方法存放到方法區的執行時常量池,具有動態性,執行期間也可以將新的常量放入池中,當常量池無法申請記憶體會丟擲OutOfMemoryError異常
7、物件訪問
:這涉及Java棧、Java堆、方法區的關聯,比如

Object obj=new Object();

Object obj反映Java棧的本地變量表,作為一個reference型別資料出現。而new Object()反映到Java堆,形成一個儲存Object型別所有例項資料值(物件中各個例項欄位的資料)的結構化記憶體,Java堆中還包含此物件型別資料(如物件型別、父類、實現的介面、方法等)的地址資訊,這些型別資料則儲存在方法區中
主要的訪問方式有兩種:使用控制代碼和直接指標
這裡寫圖片描述
注意Java堆內部有控制代碼池(優勢:reference中儲存的是穩定的控制代碼地址,物件被移動,reference毫無影響)
這裡寫圖片描述


指標訪問(優勢:速度快,節省一次指標定位的時間開銷)

實戰:OutOfMemoryError異常

1、Java堆溢位

不斷建立物件,當物件數量達到最大堆的容量限制就產生記憶體溢位異常

public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
       List<OOMObject> list=new ArrayList<OOMObject>();
       while(true){
           list.add(new OOMObject());
       }
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at 深入Java虛擬機器.HeapOOM.main(HeapOOM.java:13)

通過記憶體映像分析根據Memory Analyzar對dump出來的堆轉儲快照進行分析
這裡寫圖片描述

2、虛擬機器棧和本地方法棧溢位

棧容量由-Xss引數設定,在Java虛擬機器描述了兩種異常
1、如果執行緒請求的棧深度大於虛擬機器所允許的最大深度,就丟擲StackOverflowError異常
2、如果虛擬機器在擴充套件棧時無法申請到足夠的記憶體空間,則丟擲OutOfMemoryError異常
下面的兩種方法的結果都是StackOverflowError異常
(1)使用-Xss引數減少棧記憶體容量,結果:丟擲StackOverflowError異常,異常出現時輸出的棧深度相應縮小.
(2)定義大量的本地變數,增加此方法幀中本地變量表的長度,結果:丟擲StackOverflowError異常時輸出的棧深度相應縮小

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 (Exception e) {
            System.out.println("stack length"+oom.stackLength);
            throw e;
        }
    }
}
Exception in thread "main" java.lang.StackOverflowError

表明,在單個執行緒下,無論是棧幀太大還是虛擬機器棧容量太小,當記憶體無法分配的時候,虛擬機器都會丟擲StackOverflowError異常
作業系統32位的windows限制為2GB減去Xmx(最大堆容量),再減去MaxPermSize(最大方法區容量),剩下的記憶體由虛擬機器棧和本地方法棧“瓜分”每個執行緒分配到的棧容量越大,建立的執行緒數量就越小,建立執行緒時越容易把剩下的記憶體耗盡
如果是建立過多執行緒導致的記憶體溢位,在不能減少執行緒數量或者更換64位虛擬機器情況下,就只能通過減少最大堆和減少棧容量來換取更多的執行緒

3、執行時常量池溢位

使用String.intern()這個native給執行時常量池新增內容,該方法作用:如果已經存在在池中就返回String物件的字串,沒有就將其新增到常量池並且返回該String物件的引用,由於常量池在方法區,可以限制方法區的大小從而間接限制其中常量池的容量

4、方法區溢位

方法區用於存放Class的相關資訊,如類名、訪問修飾符、常量池、欄位、描述、方法描述等,測試思路是執行時產生大量的類去填滿方法區
在經常動態生成大量Class的應用中,需要特別注意類的回收狀況,常見:大量JSP或動態產生JSP檔案的應用(JSP第一次執行時需要編譯成Java類)、基於OSGi的應用(同一類檔案,被不同的載入器載入也會視為不同的類)