什么是 JVM
先來看下百度百科的解釋:
JVM 是 Java Virtual Machine(Java 虛擬機(jī))的縮寫距潘,JVM 是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來的計(jì)算機(jī)斯碌,是通過在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來實(shí)現(xiàn)的谱净。
晦澀難懂有沒有,簡單理解就是說虛擬機(jī)是物理機(jī)的軟件實(shí)現(xiàn)祟昭。
Java 的設(shè)計(jì)理念是 WORA(Write Once Run Anywhere颅拦,一次編寫到處運(yùn)行)蒂誉。編譯器將 Java 文件編譯為 Java .class 文件,然后將 .class 文件輸入到 JVM 中距帅,JVM 執(zhí)行類文件的加載和執(zhí)行右锨,最后轉(zhuǎn)變成機(jī)器可以識(shí)別的機(jī)器碼進(jìn)行最終的操作。
為什么要學(xué)習(xí) JVM
每個(gè) Java 開發(fā)人員都知道字節(jié)碼經(jīng)由 JRE(Java 運(yùn)行時(shí)環(huán)境)執(zhí)行碌秸。但他們或許不知道 JRE 其實(shí)是由 Java 虛擬機(jī)(JVM)實(shí)現(xiàn)绍移,JVM 分析字節(jié)碼悄窃,解釋并執(zhí)行它。作為開發(fā)人員蹂窖,了解 JVM的 架構(gòu)是非常重要的广匙,因?yàn)樗刮覀兡軌蚓帉懗龈咝У拇a。
但是 JVM 在幫我們實(shí)現(xiàn) Write Once Run Anywhere 的同時(shí)恼策,有利有弊,因?yàn)樵谶@個(gè)過程中涉及到了內(nèi)存管理潮剪,尤其是多線程情況下的內(nèi)存管理問題涣楷,所以我們更應(yīng)該學(xué)習(xí) JVM 的知識(shí)來幫助自己寫出更好的代碼。
根據(jù)上邊對 JVM 的概念介紹我們知道抗碰,JVM 的主要作用在于以下兩方面狮斗,之后我們的介紹也會(huì)以此著手。
- 軟件層面的機(jī)器碼翻譯
- 內(nèi)存管理
最近也在學(xué)習(xí)《深入理解 Java 虛擬機(jī)》這本書弧蝇,此處貼個(gè)書中的圖過來:
下邊就詳細(xì)介紹一下這張圖中的各個(gè)組件
運(yùn)行時(shí)數(shù)據(jù)區(qū)
這個(gè)區(qū)域描述的是 Java 代碼運(yùn)行時(shí)的狀態(tài)碳褒,是我們非常關(guān)注的一個(gè)狀態(tài)-程序運(yùn)行狀態(tài),因?yàn)槲覀儗懘a就是為了運(yùn)行看疗,不運(yùn)行的狀態(tài)對我們是沒什么吸引力的沙峻。說白了 Java 代碼不外乎 數(shù)據(jù) 指令 控制 這三類型語句,所以我們將 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)可以劃分為如下兩大類:
- 數(shù)據(jù)
- 方法區(qū)
- 堆(Heap)
- 指令
- 虛擬機(jī)棧
- 本地方法棧
- 程序計(jì)數(shù)器
程序計(jì)數(shù)器
定義:指向當(dāng)前線程正在執(zhí)行的字節(jié)碼指令的地址 也就是行號(hào)两芳。
注意:我們需要思考一個(gè)問題摔寨,我的當(dāng)前線程本身已經(jīng)在執(zhí)行了,為什么還要找個(gè)寄存器把他的執(zhí)行行號(hào)記錄下來呢怖辆?
因?yàn)槲覀兂绦驁?zhí)行的最小單位是線程是复,而線程在 CPU 上執(zhí)行的時(shí)候是搶占式的,這樣的話就存在線程被掛起的情況竖螃,例如:有 A B 兩個(gè)線程淑廊,如果 A 線程執(zhí)行過程被 B 線程搶占了 CPU,則需要把掛起的 A 線程 當(dāng)前執(zhí)行到的行號(hào)存儲(chǔ)下來特咆,等到 A 重新獲得 CPU 時(shí)間片執(zhí)行權(quán)的時(shí)候去程序計(jì)數(shù)器獲得上一次執(zhí)行的行號(hào)以便于繼續(xù)執(zhí)行這個(gè)程序季惩。
所以,每個(gè)線程都有自己的 程序計(jì)數(shù)器腻格,而且是互不干擾的蜀备,屬于線程私有區(qū)域
- 如果執(zhí)行的是一個(gè) Java 方法,計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址
- 如果執(zhí)行的是一個(gè) Native 方法荒叶,計(jì)數(shù)器的值則為空(undefined)
虛擬機(jī)棧
定義:存儲(chǔ)當(dāng)前線程運(yùn)行方法所需要的數(shù)據(jù)碾阁、指令和返回地址,生命周期與線程相同些楣,同樣屬于線程私有區(qū)域脂凶。
每個(gè) Java 方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量宪睹、操作數(shù)棧、方法出口等信息蚕钦,
如下所示亭病,這個(gè)棧幀會(huì)存儲(chǔ)的信息包括:
局部變量表
操作數(shù)棧
動(dòng)態(tài)鏈接
出口
... ...
每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程,其實(shí)真正對應(yīng)的是一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程嘶居。
其中局部變量表存放了編譯器可知的各種基本數(shù)據(jù)類型罪帖、引用對象等。需要注意的是因?yàn)榫植孔兞勘砜臻g長度只有 32 位邮屁,如果是 long 和 double 類型的話會(huì)占用 2 個(gè)局部變量表空間整袁,其他數(shù)據(jù)類型只占用 1 個(gè)。
注意:局部變量表所需的內(nèi)存空間在編譯期間就會(huì)車隊(duì)分配完成佑吝,因?yàn)樵谶M(jìn)入一個(gè)方法時(shí)坐昙,這個(gè)方法需要在棧幀中分配多大的局部空間是完全確定的,方法運(yùn)行期間局部變量大小是不會(huì)改變的芋忿。
本地方法棧
和虛擬機(jī)棧類似炸客,只不過他存儲(chǔ)的是當(dāng)前線程調(diào)用的本地方法所需要的數(shù)據(jù)、指令和返回地址等戈钢,本地方法時(shí)標(biāo)識(shí)有 Native 關(guān)鍵字的方法痹仙,此處就不展開描述了,參考上述虛擬機(jī)棧的介紹殉了。
另外蝶溶,根據(jù)《深入理解 Java 虛擬機(jī)》這本書的介紹,有些虛擬機(jī)(如 Sun HotSpot 虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一了宣渗。
方法區(qū)
這塊區(qū)域?qū)儆诰€程共享群與抖所,主要存儲(chǔ)的信息包括已被虛擬機(jī)你加載的類信息(類的元信息)、常量痕囱、靜態(tài)變量田轧、JIT(編譯器編譯后的代碼)等數(shù)據(jù)。
方法區(qū)有一塊區(qū)域我們稱之為 運(yùn)行時(shí)常量池鞍恢,存放編譯期生成的各種字面量和符號(hào)引用傻粘,運(yùn)行時(shí)常量池有一個(gè)重要特征是具備動(dòng)態(tài)性,也就是說在運(yùn)行期間依然可以將新的常量放入池中帮掉,我們開發(fā)常用的有 String 類的 intern() 方法
堆(Heap)
屬于線程共享區(qū)域弦悉,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建,是虛擬機(jī)管理的內(nèi)存中最大的一塊蟆炊。它的唯一作用就是存放對象實(shí)例稽莉。
根據(jù)虛擬機(jī)規(guī)范的描述是:所有的對象實(shí)例及數(shù)組都要在堆上分配。當(dāng)然隨著現(xiàn)在技術(shù)的發(fā)展優(yōu)化這個(gè)也變得沒有那么絕對涩搓,后續(xù)會(huì)進(jìn)行分享污秆。
這塊區(qū)域也是垃圾收集器管理的主要區(qū)域劈猪,現(xiàn)如今流行的垃圾回收器基本都采用的是分代收集算法,所以也就衍生了一些分代方式良拼,
比如對于內(nèi)存模型的劃分战得,在 JDK1.8 以前的版本基本是這樣的:
-
新生代
- Eden
- s0
- s1
老年代
永久代
在 JDK 1.8 以后的版本:
新生代
老年代
Meta Space
此處小提一下,之所以在 JDK 1.8 以后 有了 Meta Space庸推,其設(shè)計(jì)的目的在于規(guī)避永久代溢出的問題常侦,因?yàn)?Meta Space 是可以自動(dòng)擴(kuò)容的,就跟 Java 中的集合一樣贬媒。
以上種種的劃分方式聋亡,都是為了更好地回收內(nèi)存或者分配內(nèi)存,從下一篇開始就開始學(xué)習(xí)內(nèi)存分配及垃圾回收相關(guān)算法啦掖蛤!
總結(jié)
- JVM 負(fù)責(zé)軟件層面的機(jī)器碼翻譯,可以把我們寫的 .java 文件翻譯成機(jī)器可以識(shí)別的機(jī)器碼
- JVM 負(fù)責(zé)內(nèi)存管理
- JVM 的運(yùn)行時(shí)數(shù)據(jù)區(qū)包括方法區(qū)井厌、堆蚓庭、虛擬機(jī)棧、本地方法棧和程序計(jì)數(shù)器
- JVM 中的方法區(qū)和堆區(qū)是所有線程共享的仅仆,其他區(qū)域都是線程獨(dú)享的