新華社兩學(xué)一做網(wǎng)站seo整合營(yíng)銷
JVM 是 Java 運(yùn)行的基礎(chǔ),也是實(shí)現(xiàn)一次編譯到處執(zhí)行的關(guān)鍵,那么 JVM 是如何執(zhí)行的呢?
JVM 執(zhí)行流程
程序在執(zhí)行之前先要把java代碼轉(zhuǎn)換成字節(jié)碼(class 文件), JVM 首先需要把字節(jié)碼通過一定的
方式 類加載器(ClassLoader) ? 把文件加載到內(nèi)存中? 運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime Data Area) ,而字節(jié)碼文件是 JVM 的一套指令集規(guī)范,并不能直接交個(gè)底層操作系統(tǒng)去執(zhí)行,因此需要特定的命令解析器 執(zhí)行引擎(Execution Engine) 將 字節(jié)碼翻譯 成底層系統(tǒng)指令再交由 CPU 去執(zhí)行,而這個(gè)過程中需要調(diào)用其他語言的接口 本地庫接口(Native Interface) 來實(shí)現(xiàn)整個(gè)程序的功能,這就是這 4 個(gè)主要組成部分的職責(zé)與功能。

總結(jié)來看, JVM 主要通過分為以下 4 個(gè)部分,來執(zhí)行 Java 程序的,它們分別是:1. 類加載器(ClassLoader)2. 運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime Data Area)3. 執(zhí)行引擎(Execution Engine)4. 本地庫接口(Native Interface)
JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)?
JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)域也叫內(nèi)存布局,但需要注意的是它和 Java 內(nèi)存模型( (Java Memory Model ,簡(jiǎn)稱JMM)完全不同,屬于完全不同的兩個(gè)概念,它由以下 5 大部分組成:

2.1 堆(線程共享)
堆的作用:程序中創(chuàng)建的所有對(duì)象都在保存在堆中。

堆里面分為兩個(gè)區(qū)域:新生代和老生代,新生代放新建的對(duì)象,當(dāng)經(jīng)過一定 GC 次數(shù)之后還存活的對(duì)象會(huì)放入老生代。新生代還有 3 個(gè)區(qū)域:一個(gè) Endn + 兩個(gè) Survivor ( S0/S1 )。
垃圾回收的時(shí)候會(huì)將 Endn 中存活的對(duì)象放到一個(gè)未使用的 Survivor 中,并把當(dāng)前的 Endn 和正在使用的 Survivor 清楚掉。
2.2 Java虛擬機(jī)棧(線程私有)
Java 虛擬機(jī)棧的作用: Java 虛擬機(jī)棧的生命周期和線程相同, Java 虛擬機(jī)棧描述的是 Java 方法執(zhí)行的 內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame )用于存儲(chǔ)局部變量表、操作數(shù) 棧、動(dòng)態(tài)鏈接、方法出口等信息。咱們常說的堆內(nèi)存、棧內(nèi)存中,棧內(nèi)存指的就是虛擬機(jī)棧。
Java 虛擬機(jī)棧中包含了以下 4 部分:
![]()
- 局部變量表: 存放了編譯器可知的各種基本數(shù)據(jù)類型(8大基本數(shù)據(jù)類型)、對(duì)象引用。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的,在執(zhí)行期間不會(huì)改變局部變量表大小。簡(jiǎn)單來說就是存放方法參數(shù)和局部變量。
- 操作棧:某些字節(jié)碼指令把值壓入操作數(shù)棧,其余指令將操作數(shù)取出棧。使用他們后再把結(jié)果壓入棧。比如:執(zhí)行復(fù)制、交換、求和等操作。
- 動(dòng)態(tài)鏈接:指向運(yùn)行時(shí)常量池的方法引用。
- 方法返回地址:PC 寄存器的地址。
什么是線程私有 ?由于 JVM 的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn),因此在任何一個(gè)確定的時(shí)刻,一個(gè)處理器 ( 多核處理器則指的是一個(gè)內(nèi)核 ) 都只會(huì)執(zhí)行一條線程中的指令。因此為了切換線程后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要獨(dú)立的程序計(jì)數(shù)器,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。我們就把類似這類區(qū)域稱之為 " 線程私有 " 的內(nèi)存
2.3 本地方法棧(線程私有)
本地方法棧和虛擬機(jī)棧類似,只不過 Java 虛擬機(jī)棧是給 JVM 使用的,而本地方法棧是給本地方法使用的。
2.4 程序計(jì)數(shù)器(線程私有)
程序計(jì)數(shù)器的作用:用來記錄當(dāng)前線程執(zhí)行的行號(hào)的。
程序計(jì)數(shù)器是一塊比較小的內(nèi)存空間,可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
如果當(dāng)前線程正在執(zhí)行的是一個(gè) Java 方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;
如果正在執(zhí)行的是一個(gè) Native 方法,這個(gè)計(jì)數(shù)器值為空。
程序計(jì)數(shù)器內(nèi)存區(qū)域是唯一一個(gè)在 JVM 規(guī)范中沒有規(guī)定任何 OOM 情況的區(qū)域!
2.5 方法區(qū)(線程共享)
方法區(qū)的作用:用來存儲(chǔ)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)
的。
在《 Java 虛擬機(jī)規(guī)范中》把此區(qū)域稱之為 “ 方法區(qū) ” ,而在 HotSpot 虛擬機(jī)的實(shí)現(xiàn)中,在 JDK 7 時(shí)此區(qū)域叫做永久代(PermGen ), JDK 8 中叫做元空間( Metaspace )。
PS :永久代( PermGen )和元空間( Metaspace )是 HotSpot 中對(duì)《 Java 虛擬機(jī)規(guī)范》中方法
區(qū)的實(shí)現(xiàn),它們?nèi)咧g的關(guān)系就好比,對(duì)于一輛汽車來說它定義了一個(gè)部分叫做 “ 動(dòng)能提供裝
置 ” ,但對(duì)于不同的汽車有不同的實(shí)現(xiàn)技術(shù),比如對(duì)于燃油車來說,它的 “ 動(dòng)能提供裝置 ” 的實(shí)現(xiàn)技
術(shù)就是汽油發(fā)動(dòng)機(jī)(簡(jiǎn)稱發(fā)動(dòng)機(jī)),而對(duì)于電動(dòng)汽車來說,它的 “ 動(dòng)能提供裝置 ” 的實(shí)現(xiàn)就是電動(dòng)
發(fā)動(dòng)機(jī)(簡(jiǎn)稱電機(jī)),發(fā)動(dòng)機(jī)和電機(jī)就相當(dāng)于永久代和元空間一樣,它是對(duì)于 “ 制動(dòng)器 ” 也就是方
法區(qū)定義的實(shí)現(xiàn)。
1. 對(duì)于 HotSpot 來說, JDK 8 元空間的內(nèi)存屬于本地內(nèi)存,這樣元空間的大小就不在受 JVM 最大內(nèi)
存的參數(shù)影響了,而是與本地內(nèi)存的大小有關(guān)。
2. JDK 8 中將字符串常量池移動(dòng)到了堆中。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池是方法區(qū)的一部分,存放字面量與符號(hào)引用。
字面量 : 字符串 (JDK 8 移動(dòng)到堆中 ) 、 final常量、 基本類型的包裝類。
java中基本類型的包裝類的大部分都實(shí)現(xiàn)了常量池技術(shù),即Byte,Short,Integer,Long,Character,Boolean。這5種包裝類默認(rèn)創(chuàng)建了數(shù)值[-128,127]的相應(yīng)類型的緩存數(shù)據(jù),但是超出此范圍仍然會(huì)去創(chuàng)建新的對(duì)象。
兩種浮點(diǎn)數(shù)類型的包裝類Float,Double并沒有實(shí)現(xiàn)常量池技術(shù)。
符號(hào)引用 : 類和結(jié)構(gòu)的完全限定名、字段的名稱和描述符、方法的名稱和描述符。
- 類和結(jié)構(gòu)的完全限定名 :包括包名和類/接口名。
- 字段的名稱和描述符:是一個(gè)簡(jiǎn)短的字符串,描述了字段的類型。
- 方法的名稱和描述符:?是一個(gè)簡(jiǎn)短的字符串,描述了方法的返回類型和參數(shù)類型。
JVM 類加載?
類加載過程
從上面的圖片我們可以看出整個(gè) JVM 執(zhí)行的流程中,和程序員關(guān)系最密切的就是類加載的過程了,所以
接下來我們來看下類加載的執(zhí)行流程。
對(duì)于一個(gè)類來說,它的生命周期是這樣的

其中前 5 步是固定的順序并且也是類加載的過程,其中中間的 3 步我們都屬于連接,所以對(duì)于類加載來
說總共分為以下幾個(gè)步驟:
1. 加載
2. 連接
????????1. 驗(yàn)證
????????2. 準(zhǔn)備
????????3. 解析
3. 初始化
下面我們分別來看每個(gè)步驟的具體執(zhí)行內(nèi)容。
1) 加載
加載即Java 類的字節(jié)碼文件加載到機(jī)器內(nèi)存中,并在內(nèi)存中構(gòu)建出 Java 類的原型——類模板對(duì)象。所謂類模板對(duì)象,其實(shí)就是 Java 類在 JVM 內(nèi)存中的一個(gè)快照,JVM 將從字節(jié)碼文件中解析出的常量池、類字段、類方法等信息存儲(chǔ)到模板中,這樣 JVM 在運(yùn)行期便能通過類模板而獲取 Java 類中的任意信息,能夠?qū)?Java 類的成員變量進(jìn)行遍歷,也能進(jìn)行 Java 方法的調(diào)用。
在加載階段,虛擬機(jī)需要完成以下3件事情:
- 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
- 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。
- 在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。
2) 驗(yàn)證
驗(yàn)證是連接階段的第一步,這一階段的目的是確保Class文件的字節(jié) 流中包含的信息符合《Java虛擬機(jī) 規(guī)范》的全部約束要求,保證這些信 息被當(dāng)作代碼運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全。
驗(yàn)證選項(xiàng):
- 文件格式驗(yàn)證
- 字節(jié)碼驗(yàn)證
- 符號(hào)引用驗(yàn)證
3) 準(zhǔn)備
準(zhǔn)備階段是正式為類中定義的變量(即靜態(tài)變量,被static修飾的變量)分配內(nèi)存并設(shè)置類變量初始值 的階段。
比如此時(shí)有這樣一行代碼:
public static int value = 123;
它是初始化 value 的 int 值為 0 ,而非 123 。
4) 解析
解析階段是 Java 虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程,也就是初始化常量的過程。
5) 初始化
初始化階段,Java 虛擬機(jī)真正開始執(zhí)行類中編寫的 Java 程序代碼,將主導(dǎo)權(quán)移交給應(yīng)用程序。初始化階段就是執(zhí)行類構(gòu)造器方法的過程。
雙親委派模型
什么是雙親委派模型?
如果一個(gè)類加載器收到了類加載的請(qǐng)求,(加載1)時(shí))它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最 終都應(yīng)該傳送到最頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o 法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒有找到所需的類) 時(shí),子加載器才會(huì)嘗試自己去完成加載。


- 啟動(dòng)類加載器:加載 JDK 中 lib 目錄中 Java 的核心類庫,即$JAVA_HOME/lib目錄。 擴(kuò)展類加載器。加載 lib/ext 目錄下的類。
- 應(yīng)用程序類加載器:加載我們寫的應(yīng)用程序。
- 自定義類加載器:根據(jù)自己的需求定制類加載器。
雙親委派模型的優(yōu)點(diǎn)
1. 避免重復(fù)加載類:比如 A 類和 B 類都有一個(gè)父類 C 類,那么當(dāng) A 啟動(dòng)時(shí)就會(huì)將 C 類加載起來,那么在 B 類進(jìn)行加載時(shí)就不需要在重復(fù)加載 C 類了。
2. 安全性:使用雙親委派模型也可以保證了 Java 的核心 API 不被篡改,如果沒有使用雙親委派模
型,而是每個(gè)類加載器加載自己的話就會(huì)出現(xiàn)一些問題,比如我們編寫一個(gè)稱為 java.lang.Object
類的話,那么程序運(yùn)行的時(shí)候,系統(tǒng)就會(huì)出現(xiàn)多個(gè)不同的 Object 類,而有些 Object 類又是用戶
自己提供的因此安全性就不能得到保證了。

?