轉(zhuǎn):wx公眾號 ::石衫的架構(gòu)筆記?
jvm區(qū)域劃分:
jvm的區(qū)域劃分如下所示:
大致就是分為:程序計數(shù)器渴庆,虛擬機(jī)棧,堆,方法區(qū)襟雷,本地方法棧刃滓,這幾個部分。
接下來我們從自己寫好的Java代碼如何通過JVM來運(yùn)行的角度耸弄,來分析一下JVM里這些區(qū)域是如何支撐我們的Java代碼跑起來的咧虎。
程序計數(shù)器
假設(shè)我們有如下的一個類,就是最最基本的一個HelloWorld而已:
public class HelloWorld {
? ? ? ? public static void main(String[] args) {
? ? ? ? ? ?System.out.println("Hello World");
? ? ? ? }
}
上面那段代碼首先會存在于 “.java” 后綴的文件里计呈,這個文件就是java源代碼文件老客,但是這個文件是面向我們程序員的,計算機(jī)看不懂這段代碼
所以此時就得通過編譯器震叮,把“.java”后綴的源代碼文件編譯為“.class”后綴的字節(jié)碼文件。
這個“.class”后綴的字節(jié)碼文件里鳍鸵,存放的就是對你寫出來的代碼編譯好的字節(jié)碼了苇瓣。
這個字節(jié)碼才是計算器可以理解的一種語言,而不是我們寫出來的那一堆代碼偿乖。
這個字節(jié)碼看起來大概是下面這樣的:
說明一下:這段字節(jié)碼并不是完全對照著HelloWorld那個類來寫的击罪,就是給一段示例,讓大家知道“.java”翻譯成的“.class”是大概什么樣子贪薪。
比如這里的“0: aload_0”這樣的東西媳禁,就是“字節(jié)碼指令”,他對應(yīng)了一條一條的機(jī)器指令画切,計算機(jī)只有讀到這種機(jī)器碼指令竣稽,才知道具體應(yīng)該要干什么。
比如說字節(jié)碼指令可能會讓計算機(jī)從內(nèi)存里讀取某個數(shù)據(jù)霍弹,或者把某個數(shù)據(jù)寫入到內(nèi)存里去毫别。
總之,各種各樣的指令典格,就會指示計算機(jī)去干各種各樣的事情岛宦。
所以現(xiàn)在首先明白一點(diǎn):我們寫好的Java代碼是會被翻譯成字節(jié)碼的,對應(yīng)各種字節(jié)碼指令耍缴。那么Java代碼通過JVM跑起來的第一件事情就明確了砾肺。
接下來,在執(zhí)行字節(jié)碼指令時防嗡,JVM里的程序計數(shù)器就是用來記錄每個線程當(dāng)前執(zhí)行的字節(jié)碼指令的位置的变汪,記錄當(dāng)前線程目前執(zhí)行到了哪一條字節(jié)碼指令。
因?yàn)闀卸鄠€線程來并發(fā)的執(zhí)行各種不同的代碼本鸣,所以每個線程都有自己的一個程序計數(shù)器疫衩,專門記錄當(dāng)前這個線程目前執(zhí)行到了哪一條字節(jié)碼指令了
下圖更加清晰的展示出了他們之間的關(guān)系。
Java虛擬機(jī)棧
Java代碼在執(zhí)行的時候,一定是線程來執(zhí)行某個方法中的代碼闷煤,哪怕上面那個最基礎(chǔ)的HelloWorld代碼童芹,也會有一個main線程來執(zhí)行main方法里的代碼。
在方法里鲤拿,經(jīng)常會定義一些方法內(nèi)的局部變量假褪,比如下面這樣,就在方法里定義了一個局部變量“name”近顷。
public void sayHello() {
? ? ? ? String name = "hello";
}
所以JVM必須有一塊區(qū)域來保存每個方法內(nèi)的局部變量等數(shù)據(jù)生音,這個區(qū)域就是Java虛擬機(jī)棧
每個線程都會去執(zhí)行各種方法的代碼,方法內(nèi)還會嵌套調(diào)用其他的方法窒升,所以每個線程都有自己的Java虛擬機(jī)棧缀遍。
如果線程執(zhí)行了一個方法,那么就會為這個方法調(diào)用創(chuàng)建對應(yīng)的一個棧幀
棧幀里有這個方法的局部變量表 饱须、操作數(shù)棧域醇、動態(tài)鏈接、方法出口等東西蓉媳,但是這里別的不太好理解譬挚,先理解一個局部變量就可以。
舉例酪呻,比如一個線程調(diào)用了上面寫的 “sayHello” 方法减宣,那么就會為“sayHello”方法創(chuàng)建一個棧幀,壓入線程自己的Java虛擬機(jī)棧里面去玩荠。
在棧幀的局部變量表里就會有“name”這個局部變量漆腌。
下圖展示了這個過程。
接著如果“sayHello”方法調(diào)用了另外一個“greeting”方法 姨蟋,比如下面那樣的代碼:
這時會給“greeting”方法又創(chuàng)建一個棧幀屉凯,壓入線程的Java虛擬機(jī)棧里,因?yàn)殚_始執(zhí)行“greeting”方法了眼溶。
而且“greeting”方法的棧幀的局部變量表里會有一個“greet”變量悠砚,這是“greeting”方法的局部變量。
接著如果“greeting”方法執(zhí)行完畢了堂飞,就會把“greeting”方法對應(yīng)的棧幀從Java虛擬機(jī)棧里給出棧灌旧。
然后接下來如果“sayHello”方法也執(zhí)行完畢了,就會把“sayHello”方法也從Java虛擬機(jī)棧里出棧绰筛。
這就是JVM中的 “Java虛擬機(jī)棧?” 這個組件的作用:調(diào)用執(zhí)行任何方法的時候枢泰,都會給方法創(chuàng)建棧幀,然后入棧铝噩。
而在棧幀里存放了這個方法對應(yīng)的局部變量之類的數(shù)據(jù)衡蚂,包括這個方法執(zhí)行的其他相關(guān)的信息,方法執(zhí)行完畢之后就出棧。
Java堆內(nèi)存
JVM中有另外一個非常關(guān)鍵的區(qū)域毛甲,就是Java堆年叮,這里就是存放我們在代碼中創(chuàng)建的各種對象的,比如說下面的代碼:
public void sayHello(String name) {
? ? Student student = new Student(name);
? ? student.study();
}
上面的 “new Student(name)” 這個代碼玻募,就是創(chuàng)建了一個Student類型的對象實(shí)例只损,這個對象實(shí)例里面會包含一些數(shù)據(jù)。
比如說這個Student的“name”就是屬于這個對象實(shí)例的數(shù)據(jù)七咧,類似Student這樣的對象跃惫,就會存放在Java堆內(nèi)存里。
Java堆內(nèi)存區(qū)域里會放入類似Student的對象艾栋,然后方法的棧幀的局部變量表里爆存,會存放這個引用類型的“student”局部變量,即存放Student對象的地址蝗砾。
相當(dāng)于你可以認(rèn)為局部變量表里的“student”指向了Java堆里的Student對象终蒂。
看下圖會更加清晰一些。
方法區(qū) / Metaspace
這個方法區(qū)是在JDK 1.8以前的版本里遥诉,代表JVM中的一塊區(qū)域.
他主要是放類似Student類自己的信息的,平時用到的各種類的信息噪叙,都是放在這個區(qū)域里矮锈,還會有一些類似常量池的東西放在這個區(qū)域里。
但是在JDK 1.8以后睁蕾,這塊區(qū)域的名字改了苞笨,叫做“Metaspace”,可以認(rèn)為是“元數(shù)據(jù)空間”這樣的意思子眶,當(dāng)然他主要還是存放我們自己寫的各種類相關(guān)的信息瀑凝。
本地方法棧
其實(shí)在JDK很多底層API里,比如IO相關(guān)的臭杰,NIO相關(guān)的粤咪,網(wǎng)絡(luò)Socket相關(guān)的,如果去看他內(nèi)部的源碼渴杆,會發(fā)現(xiàn)很多地方都不是Java代碼了寥枝。
很多地方都會去走native方法,去調(diào)用本地操作系統(tǒng)里面的一些方法磁奖,可能調(diào)用的都是c語言寫的方法囊拜,或者一些底層類庫,比如下面這樣的:
public native int hashCode();
在調(diào)用這種 native 方法時比搭,就會有線程對應(yīng)的本地方法棧冠跷,這個里面也是跟Java虛擬機(jī)棧類似的,也是存放各種native方法的局部變量表之類的信息。
堆外內(nèi)存
還有一個區(qū)域蜜托,是不屬于JVM的抄囚,通過NIO中的 allocateDirect 這種API,可以在Java堆外分配內(nèi)存空間盗冷。
然后通過Java虛擬機(jī)里的 DirectByteBuffer 來引用和操作堆外內(nèi)存空間怠苔,其實(shí)很多技術(shù)都會用這種方式,因?yàn)樵谝恍﹫鼍跋乱翘牵淹鈨?nèi)存分配可以提升性能柑司。
總結(jié)
最后做一點(diǎn)總結(jié),我們的Java代碼通過JVM來運(yùn)行時:
首先一定會一行一行執(zhí)行編譯好的字節(jié)碼指令锅劝。
然后在執(zhí)行的過程中攒驰,對于方法的調(diào)用,會通過Java虛擬機(jī)棧來為每個方法創(chuàng)建棧幀來入棧和出棧故爵,而且棧幀里有方法的局部變量表玻粪。
接著對于對象的創(chuàng)建,會分配到Java堆內(nèi)存里去
對于類信息的存儲诬垂,會放在方法區(qū) / Metaspace這樣的區(qū)域里劲室。
另外有兩塊特殊的區(qū)域:
本地方法棧,是執(zhí)行native方法時候用的棧结窘,跟Java虛擬機(jī)棧是類似的
堆外內(nèi)存很洋,可以在Java堆外分配內(nèi)存空間來存儲一些對象