分析JVM的組成結(jié)構(gòu)
一、JavaSE體系
- JavaSE,Java 平臺標準版,為 Java EE 和 Java ME 提供了基礎(chǔ)。
- JDK:Java 開發(fā)工具包,JDK 是 JRE 的超集,包含 JRE 中的所有內(nèi)容,以及開發(fā)程序所需的編譯器和調(diào)試程序等工具。
- JRE:Java SE 運行時環(huán)境 ,提供庫、Java 虛擬機和其他組件來運行用 Java 編程語言編寫的程序。主要類庫,包括:程序部署發(fā)布、用戶界面工具類、繼承庫、其他基礎(chǔ)庫,語言和工具基礎(chǔ)庫。
- JVM:Java 虛擬機,負責 JavaSE 平臺的硬件和操作系統(tǒng)無關(guān)性、編譯執(zhí)行代碼(字節(jié)碼)和平臺安全性。
二、運行時數(shù)據(jù)區(qū)
- 線程私有:程序計數(shù)器、虛擬機棧、本地方法棧。
- 線程共享:堆、方法區(qū)。
三、程序計數(shù)器
3.1、什么是程序計數(shù)器
程序計數(shù)器是一塊較小的內(nèi)存空間,它的作用可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。 -- 摘自《深入理解Java虛擬機》
3.2、程序計數(shù)器有什么特點
- 程序計數(shù)器會隨著線程的啟動而創(chuàng)建,各線程之間獨立存儲,互不影響。
- 當前線程執(zhí)行的字節(jié)碼的行號指示器。
- 如果線程正在執(zhí)行的是一個 Java 方法,則指明當前線程執(zhí)行的代字節(jié)碼行數(shù)。
- 如果正在執(zhí)行的是 Natvie 方法(本地方法),這個計數(shù)器值則為空(Undefined)。
- 占用較小的內(nèi)存空間,此內(nèi)存區(qū)域是唯一一個不會出現(xiàn) OutOfMemoryError(內(nèi)存溢出) 情況的區(qū)域。
3.3、用個例子來說明
請無視我文章中取得類名,為了方便實驗演示,命名怎么快怎么來。
public class Jvm1 { public int test(){ int a = 100; int b = 200; return a + b; } }
這樣一個類, javac Jvm1.java
,編譯成Jvm1.class文件。
再使用 javap 反匯編工具javap -c Jvm1.class
看下.class文件中數(shù)據(jù)格式。
這個就是前面提到的 當前線程執(zhí)行的字節(jié)碼的行號,而程序計數(shù)器則記錄的這個數(shù)字。
- 當然這也解釋了程序計數(shù)器不存在
OutOfMemoryError
的原因,因為它記錄的只是數(shù)字,占用空間少。 - 同時也解釋了為什么執(zhí)行的是一個 Java 方法時,則指明當前線程執(zhí)行的代字節(jié)碼行數(shù)。
- 而執(zhí)行 native方法 時程序計數(shù)器為 Undefined,因為 native方法 是大多是通過C實現(xiàn)并未編譯成需要執(zhí)行的字節(jié)碼指令,所以在計數(shù)器中當然是空。
四、虛擬機棧
- 棧有什么特點? 先進后出。
- 虛擬機棧是每個線程私有的,線程在運行時,在執(zhí)行每個方法的時候都會打包成一個 棧幀,存儲了 局部變量表,操作數(shù)據(jù)棧,動態(tài)鏈接,方法出口等信息,然后放入棧。每個時刻正在執(zhí)行的當前方法就是虛擬機棧頂?shù)臈E。方法的執(zhí)行就對應(yīng)著棧幀在虛擬機棧中入棧和出棧的過程。
- 棧的大小缺省為 1M,可用參數(shù) –Xss 調(diào)整大小,例如-Xss256k。
一個例子來看看執(zhí)行 每個方法入棧出棧 的過程。
public class Jvm2 { public static void main(String[] args) { A(); } public static void A() { System.out.println("A開始"); // 此處省略100行代碼 B(); // 調(diào)用B方法 System.out.println("A結(jié)束"); } public static void B() { System.out.println("B開始"); // 此處省略100行代碼 C(); // 調(diào)用B方法 System.out.println("B結(jié)束"); } public static void C() { System.out.println("C開始"); // 此處省略100行代碼 System.out.println("C結(jié)束"); } }
輸出:
A開始
B開始
C開始
C結(jié)束
B結(jié)束
A結(jié)束
4.1、局部變量表
- 顧名思義就是局部變量的表,用于存放我們的局部變量的。
- 主要存放我們的 Java 的八大基礎(chǔ)數(shù)據(jù)類型,如果是局部的一些對象,比如我們的 Object 對象,我們只需要存放它的一個引用地址即可。(基本數(shù)據(jù)類型、對象引用、returnAddress 類型)。
4.2、操作數(shù)據(jù)棧
- 存放我們方法執(zhí)行的操作數(shù)的,它就是一個棧,先進后出的棧結(jié)構(gòu)。
- 操作數(shù)棧,就是用來操作的,操作的的元素可以是任意的 java 數(shù)據(jù)類型。
- 所以我們知道一個方法剛剛開始的時候,這個方法的操作數(shù)棧就是空的,操作數(shù)棧運行方法是會一直運行入棧/出棧的操作。
數(shù)據(jù)重疊優(yōu)化
虛擬機概念模型中每二個棧幀都是相互獨立的,但在實際應(yīng)用是我們知道一個方法調(diào)用另一個方法時,往往存在參數(shù)傳遞,這種做法在虛擬機實現(xiàn)過程中會做一些優(yōu)化,具體做法如下:令兩個棧幀出現(xiàn)一部分重疊。讓下面棧幀的一部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起,進行方法調(diào)用時就可以共用一部分數(shù)據(jù),無須進行額外的參數(shù)復(fù)制傳遞。
4.3、動態(tài)鏈接
需要類加載、運行時才能確定具體的方法。
棧幀中會持有一個引用(符號引用),該引用指向某個具體方法。
符號引用是一個地址位置的代號,在編譯的時候我們是不知道某個方法在運行的時候是放到哪里的,這時我用代號 com/enjoy/pojo/User.Say:()V 指代某個類的方法,將來可以把符號引用轉(zhuǎn)換成直接引用進行真實的調(diào)用。用符號引用轉(zhuǎn)化成直接引用的解析時機,把解析分為兩大類:
- 靜態(tài)解析:符號引用在類加載階段或者第一次使用的時候就直接轉(zhuǎn)換成直接引用。
- 動態(tài)連接:符號引用在每次運行期間轉(zhuǎn)換為直接引用,即每次運行都重新轉(zhuǎn)換。
4.4、方法出口
1、正常返回(調(diào)用程序計數(shù)器中的地址作為返回)三步曲
- 恢復(fù)上層方法的局部變量表和操作數(shù)棧
- 把返回值(如果有的話)壓入調(diào)用者棧幀的操作數(shù)棧中
- 調(diào)整 PC 計數(shù)器的值以指向方法調(diào)用指令后面的一條指令
2、異常返回
指方法執(zhí)行過程中遇到異常,并且這個異常在方法體內(nèi)部沒有得到處理,導(dǎo)致方法退出
4.5、棧溢出
- java.lang.StackOverflowError:一般的方法調(diào)用是很難出現(xiàn)的,如果出現(xiàn)了要考慮是否有 無限遞歸 ,虛擬機棧帶給我們的啟示:方法的執(zhí)行因為要打包成棧楨,所以天生要比實現(xiàn)同樣功能的循環(huán)慢,所以樹的遍歷算法中:遞歸和非遞歸(循環(huán)來實現(xiàn))都有存在的意義。遞歸代碼簡潔,非遞歸代碼復(fù)雜但是速度較快。
- OutOfMemoryError:不斷建立線程(一般演示不出,演示出來機器也死了)。
五、本地方法棧
- 本地方法棧和虛擬機棧所發(fā)揮的作用是非常相似的,其區(qū)別不過是虛擬機棧為虛擬機執(zhí)行 Java 方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機使用到的 Native 方法服務(wù)。
- 虛擬機規(guī)范中對本地方法棧中的方法使用的語言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強制規(guī)定,因此具體的虛擬機可以自由實現(xiàn)它。
- 本地方法棧 native 方法通過 JNI 調(diào)用到了底層的 C/C++(c/c++可以觸發(fā)匯編語言,然后驅(qū)動硬件)。
- 當一個JVM創(chuàng)建的線程調(diào)用native方法后,JVM不再為其在虛擬機棧中創(chuàng)建棧幀,JVM只是簡單地動態(tài)鏈接并直接調(diào)用native方法。
六、方法區(qū)
主要存儲類信息、常量池、靜態(tài)變量、即時編譯期編譯后的代碼等數(shù)據(jù)。
永久代和元空間:
方法區(qū)在 jdk1.7 及其之前又背稱為永久代,jdk1.8 又被稱為元空間,怎么理解呢?
1.jdk1.8移除了永久代,新增了元空間。
2.可以理解為方法區(qū)是一個規(guī)范,但是具體怎么實現(xiàn)要看具體的jvm怎么實現(xiàn)。
3.就類似于提供了一個接口方法(規(guī)范),只要實現(xiàn)了這個接口的類,那么就要去實現(xiàn)里面接口方法(具體實現(xiàn)就是各種版本jvm之間和版本之間的差異了)。
4.各種版本jvm 。
- HotSpot VM(SUN) 以前使用范圍最廣的Java虛擬機。
- JRockit VM(BEA) 號稱世界上最快的JVM 。
- Dalvik VM(Google) google自己開發(fā)的。
- HotSPont VM(ORACLE) 目前以前使用范圍最廣的Java虛擬機。
5.版本差異(jdk1.7, jdk1.8) 。
參數(shù)設(shè)置:
- jdk1.7 及以前:-XX:PermSize;-XX:MaxPermSize;
- jdk1.8 以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
- jdk1.8 以后大小就只受本機總內(nèi)存的限制
七、堆
- 幾乎所有對象都分配在堆內(nèi)存,也是垃圾回收發(fā)生的主要區(qū)域。
- 堆內(nèi)存由多個線程共享。堆內(nèi)存隨著JVM啟動而創(chuàng)建。
參數(shù)設(shè)置:
-Xms:堆的最小值
-Xmx:堆的最大值
-Xmn:新生代的大小
-XX:NewSize;新生代最小值
-XX:MaxNewSize:新生代最大值
八、運行時常量池
8.1、符號引用
- 一個 java 類(假設(shè)為 People 類)被編譯成一個 class 文件時,如果 People 類引用了 Tool 類,但是在編譯時 People 類并不知道引用類的實際內(nèi)存地址,因此只能使用符號引用來代替。
- 而在類裝載器裝載 People 類時,此時可以通過虛擬機獲取 Tool 類的實際內(nèi)存地址,因此便可以既將符號 org.simple.Tool 替換為 Tool 類的實際內(nèi)存地址,及直接引用地址。
- 即在編譯時用符號引用來代替引用類,在加載時再通過虛擬機獲取該引用類的實際地址。
- 以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現(xiàn)的內(nèi)存布局是無關(guān)的,引用的目標不一定已經(jīng)加載到內(nèi)存中。
8.2、字面量
- 文本字符串 String a = "abc",這個 abc 就是字面量。
- 八種基本類型 int a = 1; 這個 1 就是字面量。
- 聲明為 final 的常量。
8.3、jvm各版本運行時常量池變化
- 運行時常量池:Class 文件中的常量池(編譯器生成的各種字面量和符號引用)會在類加載后被放入這個區(qū)域。
- JDK1.6:運行時常量池在方法區(qū)(永久代)中。
- JDK1.7:運行時常量池在堆中。
- JDK1.8:去永久代:使用元空間(空間大小只受制于機器的內(nèi)存)替代永久代。
8.4、直接內(nèi)存
內(nèi)存對象分配在JVM中堆以外的內(nèi)存,也可以稱為直接內(nèi)存,這些內(nèi)存直接受操作系統(tǒng)管理(而不是JVM),這樣做的好處是能夠在一定程度上減少垃圾回收對應(yīng)用程序造成的影響。
- 使用 Native 函數(shù)庫直接分配堆外內(nèi)存(NIO)。
- 并不是 JVM 運行時數(shù)據(jù)區(qū)域的一部分,但是會被頻繁使用(可以通過-XX:MaxDirectMemorySize 來設(shè)置(默認與堆內(nèi)存最大值一樣,也會出現(xiàn) OOM 異常)。
- 避免了在 Java 堆和 Native 堆中來回復(fù)制數(shù)據(jù),能夠提高效率。
以上就是分析JVM的組成結(jié)構(gòu)的詳細內(nèi)容,更多關(guān)于JVM組成結(jié)構(gòu)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Struts2單選按鈕詳解及枚舉類型的轉(zhuǎn)換代碼示例
這篇文章主要介紹了Struts2單選按鈕詳解及枚舉類型的轉(zhuǎn)換代碼示例,分享了相關(guān)代碼示例,小編覺得還是挺不錯的,具有一定借鑒價值,需要的朋友可以參考下2018-02-02Spring配置多數(shù)據(jù)源導(dǎo)致事物無法回滾問題
這篇文章主要介紹了Spring配置多數(shù)據(jù)源導(dǎo)致事物無法回滾問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01java中利用List的subList方法實現(xiàn)對List分頁(簡單易學(xué))
本篇文章主要介紹了java中l(wèi)ist數(shù)據(jù)拆分為sublist實現(xiàn)頁面分頁的簡單代碼,具有一定的參考價值,有需要的可以了解一下。2016-11-11基于CyclicBarrier和CountDownLatch的使用區(qū)別說明
這篇文章主要介紹了基于CyclicBarrier和CountDownLatch的使用區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09