Java虛擬機器 (JVM--Java Virtual Machine)
前言
對java虛擬機器的介紹文章多如牛毛,寫本文目的在於梳理一下,也方便以後翻來看看。
另外網上文章的圖都挺醜的,本文90%的圖都出於在下親筆,如圖有錯誤,請指出,定當立即更正
本文主要介紹一下Java虛擬機器的抽象結構以及一些基礎的概念
複製程式碼
一、幾個概念簡介
1.JDK、JRE、JVM
有了JRE就能執行java程式,如果只是執行軟體,裝個JRE就行了。
我們一般說java8,java10都是指的JDK,是java開發者使用的工具集,是一個大的概念,下面是java8的JDK組成
複製程式碼
2.JDK、JRE、JVM的關係
JDK:Java 語言的軟體開發工具包(
Java Development Kit
)
JRE: Java執行時環境(Java Runtime Environment
)
JVM: Java虛擬機器(Java Virtual Machine
)
3.歷史上的java虛擬機器型別
虛擬機器即:模擬計算機功能,並提供統一操作介面,從而實現程式碼在不同平臺的一致性。
從本質上來看,JVM是一個抽象介面,它有很多實現(如下),而這些實現也只是應用程式而已。
Java發展至今JVM也有過更新迭代,也有基於不同場景下使用的JVM。
Sun Classic VM 第一款商用Java 虛擬機器,純直譯器方式執行java程式碼。(已退出歷史舞臺)
EXact VM 編譯器直譯器混合工作,很快被HotSpot VM取代(已退出歷史舞臺)
HotSpot VM 沿用至今
KVM 手機端----效率低(已退出歷史舞臺)
JRockit 專注服務端應用
J9 IBM公司
Microsoft JVM windows----平臺專用(已退出歷史舞臺)
Taobao VM 淘寶根據HotSpot VM定製
Dalvik 安卓虛擬機器,暫存器架構,執行.dex檔案(.class-->.dex)
複製程式碼
4.Java虛擬機器結構
二、Java虛擬機器結構簡述
1..class
檔案和類載入器
1-1. .class
檔案
如果對class感興趣,可以詳見官方文件,對class檔案介紹的非常細緻,以後會寫個專篇,這裡不深入。
java第一天便知道javac命令能將.java生成.class檔案,然後就上手IDE,基本也就與class檔案無緣了
1-2.類載入器子系統
關於
類載入器子系統
這裡展示也不深入,只要知道考它將java位元組碼檔案載入JVM即可
詳見:JVM篇2:[-載入器ClassLoader-]
2.執行時資料區
2-1. 程式計數器
當前執行緒私有,即每一個執行緒都有一個,通過改變它來選取下一條需要執行的位元組碼指令
記憶體空間較小,JVM中唯一不會出現OutOfMemoryError情況
如果該執行緒正在執行一個本地方法,那麼此時程式計數器的值是“undefined”
複製程式碼
2-2. Java虛擬機器棧
調節引數:-Xss =
---->[1.棧幀(StatckFrame)]-------------------------------------------
|--- 一個執行緒的每個方法在執行的同時,都會建立一個棧幀。
|--- 棧幀中儲存的有區域性變數表、操作站、動態連結、方法出口等。
|--- 當方法被呼叫時,棧幀入棧,當方法執行完成時,棧幀出棧。
---->[2.其他相關]-------------------------------------------
|--- 存取的速度快,僅次於暫存器
|--- 儲存著方法的相關區域性變數,包括各種基本資料型別,物件的引用,返回地址等。
|--- 區域性變數表的記憶體空間在編譯器完成分配,執行期不能改變其大小
---->[3.異常相關]-------------------------------------------
棧溢位: Java虛擬機器會丟擲StackOverflowError
記憶體溢位: 建立/動態擴充套件時沒有足夠的記憶體建立對應的Java虛擬機器棧,丟擲OutOfMemoryError異常
複製程式碼
2-3. Java虛擬機器棧``本地方法棧
當前執行緒私有,本地方法棧支援Native方法呼叫
HotSpot VM將本地方法棧和Java虛擬機器棧合二為一
異常情況:StackOverflowError和OutOfMemoryError
複製程式碼
關於執行緒私有和執行緒間共享,詳見:Java記憶體模型(JMM--Java Memory Model)
2-4.方法區
-XX:PermSize =
-XX:MaxPermSize =
---->[1.基本介紹]-------------------------------------------
當前執行緒共享區域:用於儲存已經被虛擬機器載入的[類資訊](即載入類時需要載入的資訊,
包括版本、field、方法、介面等資訊)、final常量、靜態變數、編譯器即時編譯的程式碼等。
---->[2.執行時常量池]-------------------------------------------
|---用於儲存編譯期就生成的字面常量、符號引用、翻譯出來的直接引用
|---儲存在執行時產生的常量(比如String類的intern方法,作用是String維護了一個常量池,如果呼叫的字元“abc”已經在常量池中,則返回池中的字串地址,否則,新建一個常量加入池中,並返回地址)
[符號引用]:編碼是用字串表示某個變數、介面的位置,直接引用就是根據符號引用翻譯出來的地址,將在類連結階段完成翻譯
---->[3.異常相關]-------------------------------------------
異常情況:OutOfMemoryError異常。
複製程式碼
方法區、永久帶和元空間簡介
方法區(Method Area):jvm規範裡面的執行時資料區的一個組成部分(介面層面)
永久帶(Perm):jdk7及之前的版本含有,是方法區的具體實現。(實現層面)
元空間(Metaspace):jdk8及之後的版本含有,是方法區的具體實現。(實現層面) # 元空間使用本地記憶體,不在JVM中
其實理解起來也不難:先定義和使用介面,再用具體實現完成工作。
複製程式碼
2-5.Java堆
Java 堆:存放物件
|---新生代:儲存新生的物件
|--- Eden(E區):存放JVM中剛生成的Java物件
|--- Survivor(S區)
|--- FromSpace (S0)
|--- ToSpace (S1)
|---老年代:儲存長期存活的物件
|--- 年齡達標(新生代中GC下存活的次數),可通過 -XX:MaxTenuringThreshold 指定
|--- 超大物件,直接進入老年代 。
非堆:儲存程式執行時長期存活的物件,比如類的後設資料、方法、常量、屬性等。
複製程式碼
-XX:NewSize= 新生代大小
-XX:SurvivorRatio= E區與兩個S區的比值 預設 8:2
-XX:NewRatio= 新生代和老年代的比值
-XX:MaxTenuringThreshold 進入老年代年齡閥值
-Xms 初始堆大小 : 預設是實體記憶體的1/64
-Xmx 最大堆大小 : 預設是實體記憶體的1/4
-XX:PermSize= 非堆記憶體初始值: 預設是實體記憶體的1/64
-XX:MaxPermSize= 最大非堆記憶體: 預設是實體記憶體的1/4
堆大小:新生代 + 老年代 + 持久代
複製程式碼
三、堆與垃圾回收機制
Minor GC : 清理新生代
Full GC : 清理整個堆空間
複製程式碼
1:MinorGC
一般新建立物件進入E區,當E區記憶體用完後,觸發MinorGC。
存活的物件最終進入SurvivorFrom
複製程式碼
2.Survivor的兩個區域From
和To
1.當E區再度裝滿
2.觸發MinorGC回收
3.存活的物件(包括From中)複製存放入To
4.To和From調換名稱
複製程式碼
3.幾個小問題:
[1].為什麼要分新生代和老年代?
|--- 物件的生存狀況不同,使用不同的回收演算法,優化GC效能
|--- 在新生代物件可能被頻繁建立或銷燬(朝生夕死),老年代物件回收較少
[2].Survivor區存在的意義?
提高物件進入老年代的門檻,減少FullGC 的次數(FullGC很耗時)
[3].S0和S1有什麼作用?
避免Survivor區空間的碎片化
複製程式碼
四、垃圾回收演算法
垃圾物件的判定
|---引用計數法
|---可達性分析
回收演算法
|---標記清除
|---複製
|---標記整理
|---分代收集
垃圾回收器:
|---Serial
|---Parnew
|---CMS
|---G1
複製程式碼
1.垃圾物件的判定
1-1.引用計數法
在物件中新增引用計數器,當該物件被引用時,引用計數器+1,引用失效時,計數器-1
物件迴圈引用會失效
複製程式碼
1-1.可達性分析
Java中GC Root包括
:
[1].虛擬機器棧中的引用的物件。
[2].方法區中的類靜態屬性引用的物件。
[3].方法區中的常量引用的物件。
[4].本地方法棧(jni)即一般說的Native的引用物件。
複製程式碼
一個物件和GC Root之間沒有連結,那麼該物件可回收
比如ObjC和ObjB
之間的連線斷開,橙色區域的物件可回收
2.回收演算法
2-1: 標記-清除(Mark-Sweep)
---->[方式]----------------------------------
[1].標記出所有需要存活的物件
[2].標記完成之後統一回收掉未被標記的物件。
---->[缺點]----------------------------------
[1].標記和清除效率都不高。
[2].會產生大量的不連續的記憶體碎片。
大量空間碎片的缺點:當程式需要為較大物件分配記憶體時,無法找到足夠的連續記憶體而出發GC。
複製程式碼
2-2:標記-整理(Mark-Compact)
---->[方式]----------------------------------
[1].標記出所有存活的物件
[2].讓所有存活的物件都向一端移動,在移動過程中清理掉未標記的物件
---->[優劣]----------------------------------
優:不會產生大量不連續記憶體碎片問題
劣:要執行較多的複製操作,效率將會變低,不適合存活率高的情況
複製程式碼
2-3: 複製演算法(Copy)
---->[方式]----------------------------------
[1].將可用記憶體按容量分成大小相等的兩塊,每次只使用其中一塊。
[2].回收時,將記憶體中存活的物件複製到另一塊記憶體,然後清理掉該記憶體空間
---->[優劣]----------------------------------
優:無空間碎片,實現簡單,執行高效
劣:可使用的記憶體降為原來一半
複製程式碼
2-4:分代收集
這不是什麼演算法,而是根據不同的代來使用何時的演算法
新生代:可回收物件較多,回收率大。
|--- 複製演算法,高效,無碎片,從E區到S區。
老年代:可回收物件少,回收率低。
|--- 標記-整理演算法,無碎片。
複製程式碼
3.垃圾回收器簡介
---->[新生代回收器:minor GC]----------------------------
[1].Serial(序列GC)-複製
[2].ParNew(並行GC)-複製
[3].Parallel Scavenge(並行回收GC)-複製
---->[老年代回收器:full GC]----------------------------
[4].Serial Old(MSC)(序列GC)-標記-整理
[5].CMS(併發GC)-標記-清除
[6].Parallel Old(並行GC)--標記-整理
---->[G1獨立完成]-------------------------------
[7].G1(JDK1.7+)
複製程式碼
好了,綜述就到這裡,具體詳情可見接下來的各篇專題。