java虛擬機學習筆記(第二部分-自動內存管理機制 第二章-java內存區域與內存溢出異常)精簡版

第二部分 自動內存管理機制

第二章 java內存區域與內存溢出異常

2.2 運行時數據區域

1、程序計數器

①、較小的內存空間,可以看做是當前線程所執行的字節碼的行號指示器。
②、java虛擬機的多線程是通過線程輪流切換並分配處理器執行時間來實現的。一個處理器(內核)在任何一個確定的時刻都只會執行一條線程中的指令。
③、爲了在線程切換後能恢復原有的執行位置。每條線程都需要一個獨立的程序計數器,各線程之間互不影響,獨立存儲。
④、如果線程正在執行一個java方法,計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果是native方法,計數器爲空(undefined)
⑤、無OutOfMemoryError情況

2、Java虛擬機棧

①、和程序計數器一樣,線程私有,生命週期與線程相同。
②、虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀,用於存儲局部變量表、操作數棧,動態鏈接,方法出口等。
③、每一個方法從調用直至執行完成的過程沒救對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
④、堆棧中的“棧”,指的就是虛擬機棧。或者說是虛擬機棧中局部變量表部分。
⑤、局部變量表存放了編譯期可知的各種基本數據類型、對象引用和returnAddress類型。
⑥、兩種異常:1、當線程請求的棧深度大於虛擬機棧所允許的深度,將拋出StackOverflowError異常;2、如果虛擬機棧可以動態擴展,擴展是無法申請到足夠的內存,會拋出OutOfMemoryError異常。

3、本地方法棧

①、與虛擬機棧發揮的作用非常相似,區別是虛擬機棧爲執行Java方法(也就是字節碼)服務;本地方法棧爲虛擬機使用Native方法服務。
②、與虛擬機棧相同,會拋出兩種異常:StackOverFlowError和OutOfMemoryError。

4、Java堆

①、被所有線程共享,在虛擬機啓動時創建。唯一目的就是存放對象實例,幾乎所有對象實例都在這裏分配內存。隨着JIT編譯期發展與逃逸分析技術成熟,棧上分配、標量替換優化技術導致所有對象在堆上分配不是“絕對”
②、Java堆是垃圾收集器管理的主要區域,從內存回收角度看,由於現在收集器基本採用分代收集算法,可以細分爲新生代和老年代;再細一點有,Eden空間,From Survivor空間,To Survivor空間等。
③、從內存分配角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝器(TLAB),但無論如何劃分,都與存放內容無關,存儲的都是對象實例,進一步劃分目的是爲了更好的回收內存,更快的分配內存。
④、Java堆可以處於物理上不連續的內存空間中,只要邏輯上連續即可。在實現時,既可以固定大小,也可以可擴展,主流都是通過-Xmx和-Xms控制可擴展。如果堆中沒有內存完成實例分配,並且堆無法擴展時,會拋出OutOfMemoryError;

5、方法區

①、與堆一樣,都是各線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯期編譯器編譯後的代碼等數據。
②、Java虛擬機規範對方法去的限制非常寬鬆,除了和堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。相對而言,垃圾收集在這個區域比較少出現,但非絕對。
③、方法區內存回收目標主要是真的常量池回收和對類型的卸載,一般來說這裏回收“成績”比較難以令人滿意,尤其是類型的卸載,條件苛刻,但是這部分區域的回收確實是必要的。
④當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError

6、運行時常量池

①、是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項是常量池,用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池。
②、一般來說,除了報錯Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。
③、重要特徵:具備動態性,Java語言並不要求常量一定只有編譯期才能產生,除預置入Class文件中常量池的內容外,運行期間也可能減新的常量放入池中。如:String類的intern()方法。
④、是方法區的一部分,則受到方法區的內存限制,常量池無法再申請到內存是會拋出OutOfMemoryError。

7、直接內存

①、直接內存並不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,但是使用頻繁,會導致OutOfMemoryError異常
②、JDK1.4中新加入了NIO(new input/output),引入了給予通道和緩衝區(buffer)的I/O方式,可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象作爲這塊內存的引用進行操作。避免了在Java堆與Native堆中來回複製數據,提高性能。
③、直接內存的分配不受Java堆大小的限制,但是收到本機總內存限制。若忽略直接內存,使得個內存區域總和大於物理內存限制,會在動態擴展時出現OutOfMemoryError

註釋:

1、類的加載過程

①、加載:查找並加載類的二進制數據;
②、連接:包括驗證、準備、解析
驗證:確保被加載的類的正確性;
準備:爲類的靜態變量分配內存,並將其初始化爲默認值
解析:把類中的符號引用轉換爲直接引用
③、初始化:爲類的靜態變量賦予正確的初始值

1、java虛擬機的符號引用和直接引用

①、在java中,一個java類將會編譯成一個class文件。在編譯時,java類並不知道引用類的實際內存地址,因此只能使用符號引用來代替。比如org.simple.People類引用org.simple.Tool類,在編譯時People類並不知道Tool類的實際內存地址,因此只能使用符號org.simple.Tool(假設)來表示Tool類的地址。而在類裝載器裝載People類時,此時可以通過虛擬機獲取Tool類 的實際內存地址,因此便可以既將符號org.simple.Tool替換爲Tool類的實際內存地址,及直接引用地址。

②、總結:JVM對於直接引用和符號引用的處理是有區別的,可以看到符號引用時,JVM將使用StringBuilder來完成字符串的 添加,而直接引用時則直接使用String來完成;直接引用永遠比符號引用效率更快,但實際應用開發中不可能全用直接引用,要提高效能可以考慮按虛擬機的思維來編寫你的程序。

③、示例 直接引用:

public class StringAndStringBuilder{
   public static void main(String[] args){    
       System.out.println ("s=" + "asdfa");
   }
}

示例 符號引用:

public class StringAndStringBuilder{
   public static void main(String[] args){    
      String s="asdfa";
        System.out.println ("s=" + s);
   }
}
/** 
 *  
 */  
package com.bzu.csh;  
  
import java.util.Random;  
  
/** 
 * @version 
 */  
class Test6 {  
    public static final int a = 6 / 2;// 編譯時就處理了,即i = 3  
    // 3;不初始化類,static代碼塊和靜態初始化塊不執行  
    public static final int b = new Random().nextInt(10);// 運行時處理,需要初始化類,都執行  
    static {  
        System.out.println("Test6  static 靜態代碼塊" + a + ":" + b);  
    }  
    {  
        System.out.println("Test6  初始化塊");  
    }  
}  
  
class Test4 extends Test6 {  
    public static final int i = 6 / 2;// 編譯時就處理了,即i = 3  
                                        // 3;不初始化類,static代碼塊和靜態初始化塊不執行  
    public static final int j = new Random().nextInt(10);// 運行時處理,需要初始化類,都執行  
    static {  
        System.out.println("Test4  static 靜態代碼塊" + i + ":" + j);  
    }  
    {  
        System.out.println("Test4  初始化塊");  
    }  
  
}  
  
public class Test3 {  
  
    /** 
     * @param args 
     *            2017年1月7日 
     */  
    static {  
        System.out.println("Test3");  
    }  
  
    public static void main(String[] args) {  
        // Test4 t = new Test4();結果如下:執行了初始化塊  
        // Test3  
        // Test6 static 靜態代碼塊3:9  
        // Test4 static 靜態代碼塊3:0  
        // Test6 初始化塊  
        // Test4 初始化塊  
        /***********************************************************/  
        // System.out.println(Test4.j);// 這個不執行初始化塊  
        // Test3  
        // Test6 static 靜態代碼塊3:4  
        // Test4 static 靜態代碼塊3:8  
        // 8  
        /***********************************************************/  
        System.out.println(Test4.b);// 不會初始化Test4,因爲b定義在了Test6  
        // Test3  
        // Test6 static 靜態代碼塊3:8  
        // 8  
  
    }  
}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章