標籤:config add 類類型 art print slot 請求 length tac
一、什麼是虛擬機器,什麼是java虛擬機器1.1虛擬機器
定義: 類比某種電腦體繫結構,執行特定指令集的軟體
分類 系統虛擬機器(VMware,Virtual Box等), 進程虛擬機器
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堆中的對象所在的記憶體,需要虛擬機器自動完成記憶體回收,這個實現過程可以不同,但是效果需要相同。
1.3java虛擬機器運行時資料區
- 在java虛擬機器規範中定義了若干中程式運行期間會使用到的儲存不同類型的地區。
- 有一些地區是全域共用的,隨著虛擬機器的啟動而建立,隨著虛擬機器退出而銷毀。有些地區是線程私人的,隨著線程開始和結束而建立和銷毀。
- 是所有java虛擬機器共同的記憶體地區概念性模型
運行時資料區域的劃分:
- 程式計數器
- java堆
- java虛擬機器棧
- 本地方法棧
- 方法區
其中 :
方法區和堆是所有線程共用資料區。
虛擬機器棧、本地方法棧、程式計數器 是線程私人的。
二、程式計數器區(pc)
- 是所有java運行時記憶體地區最小的一塊,
- 作用: 可以看做是當前線程所執行的位元組碼的行號指標。(可以看做是指標,指向當前啟動並執行那行位元組碼代碼)
- 注意:如果正在執行一個java方法,則pc記錄的是正在執行的虛擬機器位元組碼指令的地址;如果正在執行 Native方法,則pc為空白。
- 此記憶體地區是唯一一個在java虛擬機器規範中沒有規定任何OutOfMemoryError情況的地區。
三、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虛擬機器棧頂的棧幀才是有效,這個棧幀被稱為當前棧幀,與此棧幀相關聯的方法被稱為當前方法。虛擬機器中所有執行的位元組碼指令都針對當前方法和當前棧幀操作。
局部變數表概念和特徵
定義: 是一組變數值的儲存空間,用於儲存方法,參數和方法內部定義的局部變數等等。
在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
然後輸入程式:
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(); }}
四、java 堆4.1 java堆的概念
特徵:
- 全域共用
- 通常是java虛擬機器中最大的一塊記憶體地區
- 作用是作為java對象的主要儲存區
- JVMS明確要求該地區需要實現自動記憶體管理,即常說的GC,單並不限制採用哪種演算法和技術去實現
- 可能出現OutOfMemoryError
java堆的實現可以是固定大小的,也可以是動態擴充的,現在的主流虛擬機器都是按照可擴充方式的來實現java堆的,java堆沒有記憶體可用時就會拋出此 OOM 異常。
劃分方式:
- TLAB: java堆中的對象時線程共用的,所以就會產生資料競爭,為了避免這種競爭,java虛擬機器很可能會將堆又根據各個線程來劃分出若干個線程私人的記憶體緩衝區,這一類緩衝區被稱為TLAB,即執行緒區域分配緩衝。這時各個線程會在各自的TLAB中指派至,僅在TLAB用完時才會加鎖,並向java堆分配新的TLAB記憶體。 -Xmx512m(設定最大值) -Xms16m(設定最小值)
- 新生代,老年代,永久代,這是基於一種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.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.1 直接記憶體的概念和特徵
- 並非JVMS定義的標準java運行時記憶體地區
- 隨JDK1.4中加入的NIO被引入,目的是避免在java堆和Native堆中來回複製資料帶來的效能損耗
- 全域共用
- 能被自動管理,但是檢測手段上可能會有一些簡陋
- 可能出現OutOfMempryError異常
java記憶體管理