java內存管理

1、什麼是虛擬機,什麼是java虛擬機

1.1虛擬機

定義: 模擬某種計算機體系結構,執行特定指令集的軟件
分類 系統虛擬機(VMware,Virtual Box等), 進程虛擬機java

1)進程虛擬機

特色:並不會完整的模擬一個操做系統的運行環境,僅僅提供了特定指令集的運行環境
實例: JVM, Adobe Flash, FC模擬器算法

2)高級語言虛擬機

特色:把特定指令集範圍進一步限定爲高級語言。
屬於進程虛擬機的一種,若有 JVM, .NET CLR, P-Code緩存

3)java語言虛擬機

可執行java語言的高級語言虛擬機。java語言虛擬機並不必定能夠稱爲JVM, 例如:Apache Harmony數據結構

4)java虛擬機

  • 必須經過java TCK 的兼容性測試的java語言虛擬機才能稱爲java虛擬機
  • java虛擬機並不是必定要執行java程序,java虛擬機是和java編譯後的class文件相關。
  • 業界三大商用JVM : Orcale HotSpot , Oracle JRockit VM , IBM J9 VM

1.2概念模型和具體實現

共有設計,私有實現
java虛擬機規範聲明瞭概念模型,這些概念模型不約束虛擬機的具體實現,只要求虛擬機實現的效果在外部看起來和規範描述同樣便可。
例如規範中規定了java堆中的對象所在的內存,須要虛擬機自動完成垃圾回收,這個實現過程能夠不一樣,可是效果須要相同。app

1.3java虛擬機運行時數據區

  • 在java虛擬機規範中定義了若干中程序運行期間會使用到的存儲不一樣類型的區域。
  • 有一些區域是全局共享的,隨着虛擬機的啓動而建立,隨着虛擬機退出而銷燬。有些區域是線程私有的,隨着線程開始和結束而建立和銷燬。
  • 是全部java虛擬機共同的內存區域概念模型

運行時數據區域的劃分:

  • 程序計數器
  • java堆
  • java虛擬機棧
  • 本地方法棧
  • 方法區


其中 :
方法區和堆是全部線程共享數據區。
虛擬機棧、本地方法棧、程序計數器 是線程私有的。jvm

2、程序計數器區(pc)

  • 是全部java運行時內存區域最小的一塊,
  • 做用: 能夠看作是當前線程所執行的字節碼的行號指示器。(能夠看作是指針,指向當前運行的那行字節碼代碼)
  • 注意:若是正在執行一個java方法,則pc記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行 Native方法,則pc爲空。
  • 此內存區域是惟一一個在java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。

3、java虛擬機棧和本地方法棧

3.1 java虛擬機棧的概念和特徵

  • 線程私有,生命週期和線程相同
    java虛擬機棧描述的是java方法執行時候的內存概念模型,每一個方法在執行時都會建立一個棧幀(用來建立這個方法的操做數棧,局部變量表,方法出口,動態鏈接等信息)。每個方法在調用和結束的過程,就對應了一個棧幀在虛擬機的入棧和出棧的過程。
  • 後進先出(LIFO)棧
    靠後執行的方法會先優先完成。
  • 存儲棧幀,支撐java方法的調用、執行和退出
  • 可能會出現OutOfMemoryError異常和StackOverflowError異常
    線程請求的棧深度大於java虛擬機所容許的最大深度將會拋出 StackOverflowError異常。
    若java虛擬機被設計爲可動態擴展,而動態擴展時又不能夠申請到足夠的內存時就會拋出OutOfMemoryError異常。

3.2 本地方法棧的概念和特徵

  • 線程私有
  • 後進先出(LIFO)棧
  • 做用是支撐Native方法的調用、執行和退出
  • 可能會出現OutOfMemoryError異常和StackOverflowError異常
  • 一些虛擬機(如HotSpot)將java虛擬機棧和本地方法棧合併實現了

3.3 棧幀的概念和特徵

  • java虛擬機棧中存儲的內容,它被用於存儲數據和部分過程結果的數據結構,同時也被用來處理動態連接、方法和異常分派
  • 一個完整的棧幀包含: 局部變量表、操做數棧、動態連接信息、方法正常完成和異常完成信息

在編譯程序代碼時,棧幀須要的局部變量表的大小和操做數棧的深度,在編譯期間就已經徹底肯定了,java虛擬機會把這些數據徹底寫在class文件的code表中。所以一個棧幀須要分配的內存大小不會受程序運行期間變量數據的影響,而僅僅取決於具體的java虛擬機。
在一個線程裏面方法的調用鏈可能很長,不少方法可能都同時處於執行的狀態,對於執行引擎而言,在活動線程之中,只有位於java虛擬機棧頂的棧幀纔是有效的,這個棧幀被稱爲當前棧幀,與此棧幀相關聯的方法被稱爲當前方法。虛擬機中全部執行的字節碼指令都針對當前方法和當前棧幀操做。ide

局部變量表概念和特徵

定義: 是一組變量值的存儲空間,用於存儲方法,參數和方法內部定義的局部變量等等。
在java編譯器編譯class文件時,就已經肯定了該方法須要的局部變量表的最大容量,局部變量表是以槽(Slot)爲最小單位的。java虛擬機規範中沒有明確一個slot的具體佔用的內存大小,只是描述了任何slot能夠儲存的類型。性能

  • 由若干Slot組成,長度由編譯期決定
  • 單個Slot能夠存儲一個類型爲 boolean, byte, char, short, float, reference 和 returnAddress的數據,兩個Slot能夠存儲一個類型爲long或者double的數據。
    其中, reference類型表示一個對象實例的引用,經過reference能夠間接或者直接查找到變量的實例數據,還能夠經過reference直接或者間接的查找到這個對象的類型數據,而returnAddress 已經棄用。
  • 局部變量表用於方法間參數傳遞,以及方法執行過程當中存儲基礎數據類型的值和對象的引用。
    在方法執行中,若是正在調用的方法是一個實例方法,那這個方法的局部變量的第0號Slot將默認做爲存儲這個方法的實例引用,在方法之中能夠經過關鍵字diss倆訪問到這個隱含的參數。剩下的方法參數將會按照參數表的順序排列,佔用從1開始的局部變量表空間。在參數表分配完畢以後,在根據方法表中的局部變量定義順序和他們的做用域來分配剩下的局部變量Slot,爲了節省棧幀的空間,局部變量表中Slot是能夠被重用的。

操做數棧的概念和特徵

  • 單個操做數棧的元素,被稱爲Entry
  • 後進先出棧,由若干個Entry組成,操做數棧最大棧深度在編譯期間決定
  • 單個Entry便可以存儲一個java虛擬機中定義的任意數據類型的值,包括long和double類型,可是存儲long和double類型的Entry深度爲2,其餘類型的深度爲1。
  • 在方法執行過程當中,操做數棧用處存儲計算參數和計算結果;在方法調用時,操做數棧也用來準備調用方法的參數以及退出時的返回結果。
    當一個方法剛剛開始執行的時候,這個方法的操做數棧是空的,在方法執行的過程當中會遇到各類字節碼指令往操做棧中寫入和提取內容就對應了操做數棧的出棧和入棧操做。

3.4 本地變量表和操做數棧實例

在cmd中輸入測試

copy con Test.java

而後輸入程序:ui

public class Test{
        public int calc(){
                int a =100;
                int b =200;
                int c =300;
                return (a+b)*c;
        }
}

而後編譯

javac Test.java

再查看字節碼:

javap -verbose Test.class

獲得:

0: bipush        100               //把100入棧到操做數棧的棧頂
    2: istore_1                           //把操做數棧頂的元素出棧並把此元素存儲在局部變量表中的1號Slot
    3: sipush        200
    6: istore_2
    7: sipush        300
    10: istore_3
    11: iload_1                           //將局變量表中的爲1號的操做數入棧到操做數棧棧頂
    12: iload_2                        //將局變量表中的爲2號的操做數入棧到操做數棧棧頂
    13: iadd                        //將操做數棧棧頂的兩個元素出棧,而後相加入棧
    14: iload_3
    15: imul
    16: ireturn

3.5 內存異常實例

注意: 在 idea中能夠經過設置選項中的run --> Edit configuration --> configuration --> VM options 設置java虛擬機棧的大小 添加: -Xss128k 便可。
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 (Throwable  e){
            System.out.println("length" + oom.stackLength);
            throw e;
        }
    }
}

OutOfMemoryError異常

public class JavaVMStackOOM {
    private void dontStop(){
        while(true){
        }
    }

    public void stackLeakThread(){
        while(true){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM oom = new JavaVMStackOOM();
        oom.stackLeakThread();
    }
}

4、java 堆

4.1 java堆的概念

特徵:

  • 全局共享
  • 一般是java虛擬機中最大的一塊內存區域
  • 做用是做爲java對象的主要存儲區
  • JVMS明確要求該區域須要實現自動內存管理,即常說的GC,單並不限制採用哪一種算法和技術去實現
  • 可能出現OutOfMemoryError
    java堆的實現能夠是固定大小的,也能夠是動態擴展的,如今的主流虛擬機都是按照可擴展方式的來實現java堆的,java堆沒有內存可用時就會拋出此 OOM 異常。

劃分方式:

  1. TLAB: java堆中的對象時線程共享的,因此就會產生數據競爭,爲了不這種競爭,java虛擬機極可能會將堆又根據各個線程來劃分出若干個線程私有的內存緩衝區,這一類緩衝區被稱爲TLAB,即線程本地分配緩存。這時各個線程會在各自的TLAB中分配對象,僅在TLAB用完時纔會加鎖,並向java堆分配新的TLAB內存。 -Xmx512m(設置最大值) -Xms16m(設置最小值)
  2. 新生代,老年代,永久代,這是基於一種GC回收內存的算法來劃分的。

4.2 棧和堆

從棧到堆的關聯過程:

第二種實現方式:

對比:
第二種方式中,reference中存儲的是穩定的句柄的地址,在對象被移動時(對象常常被移動,垃圾回收時會發生移動),只會改變句柄池中的到對象類型數據的指針,而reference不會改變。
第一種方式,速度更快,對比使用句柄的方式,節省了指針開銷,reference直接指向了對象的實例數據。

4.3 java堆內存異常實戰

可能發生的異常:

  • 若是實際所需的堆超過了自動內存管理系統能提供的最大容量,那java虛擬機將會拋出一個OOM異常。java堆是出現內存異常機率最大的區域。

出現OOM實例以下:
不斷的建立對象,而且保證這些內存不被回收便可作到。

public class javaHeapOOM {
    static class OOMObject{

    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while(true){
            list.add(new OOMObject());
        }
    }
}

5、方法區和運行時常量池

5.1 方法區的概念

  • 全局共享
  • 做用是存儲java類的結構信息
  • JVMS不要求該區域實現自動內存管理,可是基本都能實現自動管理該區域的內存
  • 可能出現OutOfMemoryError異常

注意:
類實例數據 和 類類型信息
實例數據是指在類中定義的各類實例對象以及它們的值,而類型信息是指定義在類代碼中的常量,靜態變量,類中聲明的各類方法,字段等,還會包括即時編譯器編譯產生的數據。

5.2 運行時常量池的概念

  • 全局共享
  • 是方法區的一部分
  • 做用是存儲java類文件常量池中的符號信息

5.3 HotSpot 方法區實現的變遷

永久代與方法區

  • 在JDK1.2~JDK1.6, HotSpot使用永久代實現方法區
  • 在JDK7開始,HotSpot開始了移除永久代的計劃
    • 符號表被移到Native Heap中
    • 字符串常量和類的靜態引用被移到Java Heap中
  • 在JDK8開始,永久代已被元空間(MetaSapce)所代替

5.4 方法區內存異常實戰

永久代變遷過程致使的異常差別:
實例1:

public class RuntimeConstantPoolChange {
    public static void main(String[] args) {
        String str1 = new StringBuilder("hptj").append("zzj").toString();
        System.out.println(str1.intern() == str1);

        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }
}

intern方法:按期的維護了一個字符串池,若是字符串中有這個字符串,則會返回這個池中常量的地址,若是這個字符串中沒有這個字符串所需的常量,則jvm會先把這個字符串加到字符串常量池中,而後再返回這個字符串的地址。

可知:java1.7開始後字符串常量池被移到了Heap中,
當採用jdk1.6運行時,則出現 false false ,由於 str1.intern返回的是常量池中的地址,而str1本生的地址是在heap中的是不可能相同的。
當採用jdk1.7運行時,則出現 true false , 由於 str1.intern和heap都是堆中的地址,則這個hptjzzj字符串在heap中沒有出現過,則會加入到heap中的常量池中,並返回此地址和str1在堆中的地址一致,而java字符串在堆常量池中已經存在,當new以後會新建一個對象,因此是不相等的。

6、直接內存

6.1 直接內存的概念和特徵

  • 並不是JVMS定義的標準java運行時內存區域
  • 隨JDK1.4中加入的NIO被引入,目的是避免在java堆和Native堆中來回複製數據帶來的性能損耗
  • 全局共享
  • 能被自動管理,可是檢測手段上可能會有一些簡陋
  • 可能出現OutOfMempryError異常
相關文章
相關標籤/搜索