摘要:在本文,筆者將與大家概覽的體系結(jié)構(gòu)與工作方式。將第條和第條指令分別是將兩個局部變量入棧,然后相加。最后一條指令是,這條指令執(zhí)行完后當(dāng)前的這個方法對應(yīng)的這些部件會被回收,局部變量區(qū)的所有值將全部釋放,寄存器會被銷魂,在棧中與這個方
Java之所以號稱“一次編譯,到處運(yùn)行”,主要原因是JVM屏蔽了各個計(jì)算機(jī)平臺相關(guān)的軟件(大多指系統(tǒng))或者硬件之間的差異,使得與平臺相關(guān)的耦合統(tǒng)一由JVM提供者來實(shí)現(xiàn)。在本文,筆者將與大家概覽JVM的體系結(jié)構(gòu)與工作方式。
JVM體系結(jié)構(gòu)詳解JVM和實(shí)體機(jī)器的體系結(jié)構(gòu)有點(diǎn)相似,主要由以下幾個部分組成:
自己的指令集(篇幅過大,這里不會描述)
類加載器(在JVM啟動時(shí)或者在類運(yùn)行時(shí)將需要的class加載到JVM中)
執(zhí)行引擎(執(zhí)行引擎的任務(wù)是負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令,相當(dāng)于實(shí)際機(jī)器上的CPU)
內(nèi)存區(qū)(將內(nèi)存劃分成若干區(qū)以模擬實(shí)際機(jī)器上的存儲、記錄和調(diào)度功能模塊,如實(shí)際機(jī)器上的各種功能的寄存器或者PC指針的記錄器等)
本地方法調(diào)用(調(diào)用C或C++實(shí)現(xiàn)的本地方法的代碼返回結(jié)果)
下面簡單介紹一下
執(zhí)行引擎執(zhí)行引擎是JVM的核心部分,執(zhí)行引擎的作用就是解析JVM字節(jié)碼指令,得到執(zhí)行結(jié)果。JVM虛擬機(jī)規(guī)范詳細(xì)地定義了執(zhí)行引擎遇到每條字節(jié)碼指令時(shí)應(yīng)該處理什么,并且應(yīng)該得到什么結(jié)果。但是并沒有規(guī)定執(zhí)行引擎應(yīng)該如何或采用什么方式處理而得到這個結(jié)果。因?yàn)閳?zhí)行引擎具體采取什么方式由JVM的實(shí)現(xiàn)廠家自己去實(shí)現(xiàn),是直接解釋執(zhí)行還是采用JIT轉(zhuǎn)換成本地代碼去執(zhí)行,還是采用寄存器這個芯片模式去執(zhí)行都可以。所以執(zhí)行引擎的具體實(shí)現(xiàn)有很大的發(fā)揮空間,如SUN的hotspot的基于棧的執(zhí)行引擎,而Google的Dalvik的基于寄存器的執(zhí)行引擎。
執(zhí)行引擎也就是執(zhí)行一條條代碼的一個流程,而代碼都是包含在方法體內(nèi)的,所以執(zhí)行引擎本質(zhì)上就是執(zhí)行一個個方法所串起來的流程,對應(yīng)到操作系統(tǒng)中一個執(zhí)行流程是一個Java進(jìn)程還是一個Java線程呢?很顯然是后者,因?yàn)橐粋€Java進(jìn)程可以有多個同時(shí)執(zhí)行的執(zhí)行流程。這樣說來每個Java線程就是一個執(zhí)行引擎的實(shí)例,那么在一個JVM實(shí)例中就會同時(shí)有多個執(zhí)行在引擎在工作,這些執(zhí)行引擎有的在執(zhí)行用戶的程序,有的在執(zhí)行JVM內(nèi)部的程序(如Java垃圾收集器)。
Java內(nèi)存管理執(zhí)行引擎在執(zhí)行一段程序時(shí)需要存儲一些東西,如:操作碼需要的操作數(shù),操作碼執(zhí)行結(jié)果需要保存。Class類的字節(jié)碼還有類的對象等信息都需要在執(zhí)行引擎執(zhí)行之前就準(zhǔn)備好。一個JVM實(shí)例會有一個方法區(qū)、Java堆、Java棧、PC寄存器和本地方法區(qū)。其中方法區(qū)和Java堆是所有線程共享的,也就是可以被所有執(zhí)行引擎實(shí)例訪問。每個新的執(zhí)行引擎實(shí)例被創(chuàng)建時(shí)會為這個執(zhí)行引擎創(chuàng)建一個Java棧和一個PC寄存器,如果當(dāng)前正在執(zhí)行一個Java方法,那么在當(dāng)前的這個Java棧中保存的是該線程中方法調(diào)用的狀態(tài),包括方法的參數(shù)、方法的局部變量、方法的返回值以及運(yùn)算的中間結(jié)果等。而PC寄存器會指向即將執(zhí)行的下一個指令。
如果是本地方法調(diào)用,則存儲在本地方法調(diào)用棧中或者特定實(shí)現(xiàn)中的某個內(nèi)存區(qū)域中。
考慮到篇幅大小,故另寫一篇文章:淺析JVM之內(nèi)存管理
JVM工作機(jī)制之前簡單分析了JVM的基本結(jié)構(gòu),下面再簡單分析一下JVM是如何執(zhí)行字節(jié)命令的,也就是前面介紹的執(zhí)行引擎是如何工作的。
我們知道,計(jì)算機(jī)只接受機(jī)器指令,其他高級語言必須先經(jīng)過編譯器編譯成機(jī)器指令才能被計(jì)算機(jī)正確執(zhí)行。然而機(jī)器語言一般和硬件平臺密切相關(guān)(指令集、CPU架構(gòu)的因素等),但高級語言會屏蔽所有底層硬件平臺甚至軟件平臺。之所以可以屏蔽是因?yàn)橹虚g有個編譯環(huán)節(jié),與硬件耦合的麻煩就交給了編譯器。所以,想說的是:編譯器和操作系統(tǒng)的關(guān)系非常密切。比如C語言在win下編譯器為Microsoft C,而Linux下通常是gcc。
通常一個程序從編寫到執(zhí)行會經(jīng)歷以下階段:
源代碼(source code)
預(yù)處理器(preprocessor)
編譯器(compiler)
匯編程序(assembler)
目標(biāo)代碼(object code)
鏈接器(linker)
可執(zhí)行程序(executables)
除了1、7兩步,其他都是由現(xiàn)代意義上的編譯器統(tǒng)一完成的。最常見的栗子是在Linux平臺下我們通常安裝一個軟件需要經(jīng)過configure、make、make install、make clean這4個步驟來完成。
configure為這個程序在當(dāng)前的操作系統(tǒng)環(huán)境下選擇適合的編譯器來編譯這個程序代碼,也就是為這個程序代碼選擇合適的編譯器和一些環(huán)境參數(shù);
make 可以猜到:對程序代碼進(jìn)行編譯操作,將源碼編譯可執(zhí)行的目標(biāo)文件
make install 將已經(jīng)編譯好的可執(zhí)行文件安裝到操作系統(tǒng)指定或者默認(rèn)的安裝目錄下
make clean 用刪除編譯時(shí)臨時(shí)產(chǎn)生的目標(biāo)文件
值得注意的是,我們通常所說的是編譯器都是將某種高級語言直接編譯成可執(zhí)行的目標(biāo)機(jī)器語言(實(shí)際上在某種操作系統(tǒng)中是ixuyao動態(tài)連接的二進(jìn)制文件:在Windows下是dynamic link library,Dll;在linux下是Shared library,SO庫)。但是實(shí)際上還有一些編譯是將一種高級語言編譯成另一種高級語言,或者將低級語言編譯成高級語言(反編譯),或者將高級語言編譯成虛擬機(jī)目標(biāo)語言,如Java編譯器等。
再回到如何讓機(jī)器(不管是實(shí)體機(jī)還是虛擬機(jī))執(zhí)行代碼的主題,不管是如何指令集都只有集中最基本的元素:加、減、乘、除、求余、求模等。這些運(yùn)算又可以進(jìn)一步分解成二進(jìn)制位運(yùn)算:與、或、異或等。這些運(yùn)算又通過指令來完成,而指令的核心目的就是確定需要運(yùn)算的種類(操作碼)和運(yùn)算需要的數(shù)據(jù)(操作數(shù)),以及從哪里(寄存器或棧)獲取操作數(shù)、將運(yùn)算結(jié)果存放到什么地方(寄存器或是棧)等。這種不同的操作方式又將指令劃分成:一地址指令、二地址指令、三地址指令和零地址指令等n地址指令。相應(yīng)的指令集會有對應(yīng)的架構(gòu)實(shí)現(xiàn),如基于寄存器的架構(gòu)實(shí)現(xiàn)或基于棧的架構(gòu)實(shí)現(xiàn),這里的基于寄存器或棧都是指在一個指令中的操作數(shù)是如何存取的。
JVM為何選擇基于棧的架構(gòu)學(xué)過數(shù)據(jù)結(jié)構(gòu)的小伙伴都知道,對棧進(jìn)行操作是要先將所有的操作數(shù)壓入棧,然后根據(jù)指令中操作碼選擇一定的元素彈出計(jì)算后再壓入棧。相對于寄存器操作(將兩個操作數(shù)存入寄存器后進(jìn)行加法運(yùn)算后再將加過存入其中一個寄存器即可)是比較麻煩的。那么,JVM為什么還要基于棧來設(shè)計(jì)呢?
JVM要設(shè)計(jì)成與平臺無關(guān)
有些平臺上的寄存器很少或者根本沒有,而且以處理器架構(gòu)的角度來說,設(shè)計(jì)一套通用的寄存器指令是很困難的。比如在android上,google的Dalvik VM就是基于ARM平臺設(shè)計(jì)的寄存器架構(gòu),這樣性能上的確更優(yōu)了,但是犧牲了跨平臺的移植性。
為了指令的緊湊型
執(zhí)行引擎的架構(gòu)設(shè)計(jì)每當(dāng)創(chuàng)建一個新的線程時(shí),JVM會為這個線程創(chuàng)建一個Java棧,同時(shí)會為這個線程分配一個PC寄存器,并且這個PC寄存器會指向這個線程的第一行可執(zhí)行代碼。每當(dāng)調(diào)用一個新方法時(shí)會在這個棧上創(chuàng)建一個新的棧幀數(shù)據(jù)結(jié)構(gòu),這個幀棧會保留這個方法的一些元信息——如這個方法中定義的局部變量、一些用來支持常量池的解析、正常方法返回及異常處理機(jī)制等。
JVM調(diào)用某些指令時(shí)可能需要使用到常量池中的一些常量,或者是獲取常量代表的數(shù)據(jù)或者這個數(shù)據(jù)指向的實(shí)例化對象,而這些信息都存儲在所有線程共享的方法區(qū)和Java堆中。
執(zhí)行引擎的執(zhí)行過程下面以一個簡單的程序來說明執(zhí)行引擎的執(zhí)行過程。
public class Math{ public static void main(String[]args){ int a=1; int b=2; int c = (a+b)*10; } }
其中對應(yīng)的字節(jié)碼指令如下:
偏移量 | 指令 | 說明 |
---|---|---|
0 | iconst_1 | 常數(shù)1入棧 |
1 | istore_1 | 將棧頂元素移入本地變量1存儲 |
2 | iconst_1 | 常數(shù)2入棧 |
3 | istore_2 | 將棧頂元素移入本地變量1存儲 |
4 | iload_1 | 本地變量1入棧 |
5 | iload_2 | 本地變量2入棧 |
6 | iadd | 彈出棧頂兩個元素相加 |
7 | bipush 10 | 將10入棧 |
9 | imul | 棧頂兩個元素相乘 |
10 | istore_3 | 棧頂元素移入本地變量3存儲 |
11 | return | 返回 |
對應(yīng)到執(zhí)行引擎的各執(zhí)行部件如圖
在開始執(zhí)行方法之前,PC寄存器存儲的指針是第1條指令的地址,局部變量區(qū)和操作棧都沒有數(shù)據(jù)。從第1條和第4條指令分別將a、b兩個本地變量賦值,對應(yīng)到局部變量區(qū)就是1和2分別存儲常數(shù)1和2。
前4條指令執(zhí)行完后,PC寄存器當(dāng)前指向的是下一條指令地址,也就是第5條指令,這時(shí)局部變量區(qū)已經(jīng)保存了兩個局部變量(也就是變量a和變量b的值),而操作棧里仍然沒有值,因?yàn)閮纱纬?shù)入棧后又分別出棧了。
將第5條和第6條指令分別是將兩個局部變量入棧,然后相加。如圖
1先入棧2后入棧,棧頂元素是2,第7條指令是棧頂?shù)膬蓚€元素彈出后相加,將結(jié)果再入棧,這時(shí)整個部件狀態(tài)如圖
可以看出,變量a和變量b想加的結(jié)果3存在當(dāng)前棧的棧頂中,接下來是第8條指令將10入棧,如圖
當(dāng)前PC寄存器執(zhí)行的地址是9,下一個操作是將當(dāng)前棧的兩個操作數(shù)彈出進(jìn)行相乘并把結(jié)果壓入棧,如圖
第10條指令是將當(dāng)前的棧頂元素存入局部變量3中,這是狀態(tài)如圖
第10條指令執(zhí)行完后棧中元素出棧,出棧的元素存儲在局部變量區(qū)3中,對應(yīng)的是變量c的值。最后一條指令是return ,這條指令執(zhí)行完后當(dāng)前的這個方法對應(yīng)的這些部件會被JVM回收,局部變量區(qū)的所有值將全部釋放,PC寄存器會被銷魂,在Java棧中與這個方法對應(yīng)的棧幀將消失。
JVM方法調(diào)用棧JVM的方法調(diào)用分別為兩種:
Java方法調(diào)用
本地方法調(diào)用
由于本地方法調(diào)用各個虛擬機(jī)的實(shí)現(xiàn)不太相同,所以這里主要介紹Java的方法調(diào)用情況。
public class Math{ public static void main(String[]args){ int a =1; int b=2; int c=math(a,b)/10; } public static int math(int a, int b){ return (a+b)*10; } }
那么其中兩個方法對應(yīng)的字節(jié)碼分別如下:
public static void main(java.lang.String[]); Code: 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: invokestatic #2; //Method math:(II) 9: bipush 10 11: idiv 12: istore_3 13: return public static int math(int ,int ); Code: 0: iload_0 1: iload_1 2: iadd 3: bipush 10 5: imul 6: ireturn
當(dāng)JVM執(zhí)行main方法時(shí),首先將常數(shù)1和2分別存儲到局部變量區(qū)1和2中,然后調(diào)用靜態(tài)math方法。從math的字節(jié)碼指令可以看出,math方法的兩個參數(shù)也存儲在其對應(yīng)的方法棧幀中的局部變量區(qū)0和1中,先將這兩個局部變量分別入棧,然后進(jìn)行相加操作再和常數(shù)10相乘。
那么來看一下實(shí)際的操作,如圖
上圖是JVM執(zhí)行到第5條指令時(shí),執(zhí)行引擎各部件的狀態(tài)圖,PC寄存器指向的是下一條執(zhí)行math方法的地址。當(dāng)執(zhí)行invokestatic指令時(shí)JVM會為math方法創(chuàng)建一個新的棧幀,并且將兩個參數(shù)存在math方法的棧幀的前兩個局部變量區(qū)中,這時(shí)PC寄存器會清零,并且會指向math方法對應(yīng)棧幀的第一條指令地址,這時(shí)的狀態(tài)如下圖
執(zhí)行invokestatic指令時(shí),創(chuàng)建了一個新的棧幀,這是棧幀的局部變量中已經(jīng)有了兩個變量了,這兩個變量是從main方法的棧幀中的操作棧中傳過來的。當(dāng)執(zhí)行math方法時(shí),math方法對應(yīng)的棧幀成為當(dāng)前的活動棧幀,PC寄存器保存的是當(dāng)前這個戰(zhàn)爭中的下一條指令地址,所以是0。
math方法先將a、b兩個變量相加,再乘以10,最后返回這個結(jié)果執(zhí)行到第5條指令的狀態(tài),如下圖
math的操作棧中的棧頂元素相乘的結(jié)果是30,最后一條指令是ireturn,這條指令是將當(dāng)前棧幀中的棧頂元素返回到調(diào)用這個方法的棧中,而這個棧幀也將撤銷,PC寄存器的值回復(fù)調(diào)用棧的下一條指令地址,如下圖
main方法將math方法返回的結(jié)果再除以10存放在變量區(qū)3中,這時(shí)的狀態(tài)如圖所示
當(dāng)執(zhí)行return指令時(shí)main方法對應(yīng)的棧幀也將撤銷,如果當(dāng)前線程對應(yīng)的Java棧中沒有棧幀,這個Java棧也將被JVM撤銷,整個JVM退出。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/65148.html
摘要:用于列舉最近分析過的文件常用功能欄,從左到右依次是概覽類直方圖支配樹查詢線程視圖報(bào)告相關(guān)詳細(xì)功能。針對那些占用堆內(nèi)存超過整個堆內(nèi)存大小的組件做一系列的分析,例如保留集合潛在的內(nèi)存浪費(fèi)問題等其他問題。 上一篇文章MAT入門到精通(一)介紹了MAT的使用場景和基本概念,這篇文章開始介紹MAT的基本功能,后面還有兩篇,一篇是MAT的高級功能,另一篇是MAT實(shí)戰(zhàn)案例分析。 三、歡迎頁 使用MA...
摘要:堆區(qū)堆是虛擬機(jī)所管理的內(nèi)存中最大的一塊,它是被所有線程共享的一塊內(nèi)存區(qū)域,該區(qū)域在虛擬機(jī)啟動的時(shí)候創(chuàng)建。 運(yùn)行時(shí)數(shù)據(jù)區(qū)域 ? ?想要了解jvm,那對其內(nèi)存分配管理的學(xué)習(xí)是必不可少的;java虛擬機(jī)在執(zhí)行java程序的時(shí)候會把它所管理的內(nèi)存劃分成若干數(shù)據(jù)區(qū)域。這些區(qū)域有著不同的功能、用途、創(chuàng)建/銷毀時(shí)間。java虛擬機(jī)所分配管理的內(nèi)存區(qū)域如圖1所示 程序計(jì)數(shù)器 ? ?程序計(jì)數(shù)器是一塊比較...
摘要:點(diǎn)擊進(jìn)入我的博客命令行工具這些工具大多數(shù)是類庫的一層薄的包裝,它們的主要功能代碼是在類庫中實(shí)現(xiàn)的。可視化工具是到目前為止隨發(fā)布的功能最強(qiáng)大的運(yùn)行監(jiān)視和故障處理程序,并且可以預(yù)見在未來一段時(shí)間內(nèi)都是官方主力發(fā)展的虛擬機(jī)故障處理工具。 點(diǎn)擊進(jìn)入我的博客 3.1 JDK命令行工具 showImg(https://segmentfault.com/img/remote/14600000174...
摘要:做好的優(yōu)化能大大提升系統(tǒng)的性能體系結(jié)構(gòu)概覽大致流程如圖編譯好的文件通過類加載器從物理結(jié)構(gòu)轉(zhuǎn)換成運(yùn)行時(shí)數(shù)據(jù)區(qū)結(jié)構(gòu)。后面再寫一篇關(guān)于調(diào)優(yōu)的 什么是jvm jvm是java虛擬機(jī)的縮寫。所有的java程序都是在jvm上運(yùn)行的。做好jvm的優(yōu)化能大大提升系統(tǒng)的性能 jvm體系結(jié)構(gòu)概覽 showImg(https://segmentfault.com/img/bVba5lB?w=1049&h=6...
摘要:正文架構(gòu)概覽正文架構(gòu)概覽接觸大概一個月吧,期間寫了個項(xiàng)目,趁現(xiàn)在稍微有點(diǎn)時(shí)間,來回顧梳理一下。里的模塊,并不等同于項(xiàng)目中的模塊概念。當(dāng)然,這只是我目前階段的理解。聲明 本系列文章內(nèi)容梳理自以下來源: Angular 官方中文版教程 官方的教程,其實(shí)已經(jīng)很詳細(xì)且易懂,這里再次梳理的目的在于復(fù)習(xí)和鞏固相關(guān)知識點(diǎn),剛開始接觸學(xué)習(xí) Angular 的還是建議以官網(wǎng)為主。 因?yàn)檫@系列文章,更多的會...
閱讀 3034·2023-04-26 03:01
閱讀 3538·2023-04-25 19:54
閱讀 1592·2021-11-24 09:39
閱讀 1374·2021-11-19 09:40
閱讀 4250·2021-10-14 09:43
閱讀 2062·2019-08-30 15:56
閱讀 1490·2019-08-30 13:52
閱讀 1660·2019-08-29 13:05