Java虛擬機(jī)規(guī)范(Java SE 8版)讀后總結(jié)

寫在前面:
因為之前讀過周志明的《深入理解Java虛擬機(jī)》续担,并且也一直在閱讀相關(guān)的博客,所以對Java虛擬機(jī)的知識有了一點淺顯的了解(主要是在內(nèi)存分配、垃圾回收察署、類加載以及內(nèi)存模型方面)涝桅。但是個人感覺class文件格式以及字節(jié)碼指令集等方面不太了解拜姿,所以就去圖書館借來這本書打算學(xué)習(xí)學(xué)習(xí)。由于臨近期末冯遂,要應(yīng)付各種課設(shè)以及實驗以至于前后花了兩周多的時間才把它簡單地過了一遍蕊肥。


Java虛擬機(jī)規(guī)范 Java SE 8版封面

全書一共7章大概300多頁,之所以叫規(guī)范蛤肌,也就是書中僅僅描述了抽象的Java虛擬機(jī)壁却,而在實現(xiàn)具體的Java虛擬機(jī)時批狱,本書指出了設(shè)計規(guī)范。Java虛擬機(jī)的實現(xiàn)必須體現(xiàn)書中內(nèi)容儒洛,但僅在確有必要時才應(yīng)該受制于這些規(guī)范精耐。用書中的原話來說即"公有設(shè)計,私有實現(xiàn)"琅锻。

各章摘要

  • 第1章 :簡單地介紹了Java虛擬機(jī)的歷史并吹捧了←_← 一下Java的平臺無關(guān)性(一次編譯卦停,到處運行);
  • 第2章:概覽Java虛擬機(jī)整體架構(gòu)恼蓬;
  • 第3章:介紹如何將Java語言編寫的程序轉(zhuǎn)換為虛擬機(jī)指令集惊完;
  • 第4章:定義class文件格式。它是一種與硬件和操作系統(tǒng)無關(guān)的二進(jìn)制格式处硬,用來表示編譯后的類和接口小槐;
  • 第5章:定義了Java虛擬機(jī)啟動以及類和接口的加載、鏈接和初始化的過程荷辕;
  • 第6章:定義了Java虛擬機(jī)指令集凿跳;
  • 第7章:提供了一張以操作碼值為索引的Java虛擬機(jī)操作碼助記表。

一開始我直接就捧著介紹class文件格式以及字節(jié)碼指令集的部分生啃疮方,后來發(fā)現(xiàn)完全記不住控嗜。所以后來我就通過閱讀書上介紹的一些內(nèi)容,再結(jié)合書中講解字節(jié)碼的部分再來理解骡显,學(xué)習(xí)效率提升了不少疆栏。因此在本文中對知識總結(jié)的順序和原書會有很大的不一樣,并且本文主要是個人的學(xué)習(xí)總結(jié)惫谤,如果有想深入學(xué)習(xí)Java虛擬機(jī)規(guī)范的同學(xué)壁顶,我建議還是閱讀原書比較好。

要去正確地實現(xiàn)一臺Java虛擬機(jī)溜歪,就需要正確地讀取class文件中每一條字節(jié)碼指令并且能正確執(zhí)行這些指令所蘊含的操作即可若专。

數(shù)據(jù)類型

和Java語言類似,在Java虛擬機(jī)中的數(shù)據(jù)類型也可以分為基本類型引用類型兩種蝴猪,所以也存在原始值和引用值兩種類型的數(shù)值富岳。它們可用于變量賦值、參數(shù)傳遞拯腮、方法返回和運算操作。

原始類型與值

Java虛擬機(jī)所支持的原始數(shù)據(jù)類型包括數(shù)值類型蚁飒、boolean類型动壤、和returnAddress類型

  • 數(shù)值類型分為整數(shù)類型和浮點類型,分別是char,byte,short,int,long;浮點類型即float和double,這里和Java語言中的一致淮逻。
  • returnAddress翻譯過來是返回地址琼懊,其實returnAddress類型的值指向一條虛擬機(jī)指令的操作碼阁簸。它在虛擬機(jī)中比較典型的一個應(yīng)用場景是用于jsr程序段落跳轉(zhuǎn),在try-catch異常處理以及finally代碼塊經(jīng)常出現(xiàn)哼丈。和數(shù)值類的原生類型不同启妹,returnAddress類型在Java語言之中并不存在相應(yīng)的類型,而且也無法在程序運行期間修改醉旦。
  • 雖然Java虛擬機(jī)定義了boolean這種數(shù)據(jù)類型饶米,但是只對它提供了十分有限的支持。在Java虛擬機(jī)中并沒有任何供boolean值專用的字節(jié)碼指令车胡,Java語言表達(dá)式所操作的boolean值檬输,在編譯之后都使用Java虛擬機(jī)中的int數(shù)據(jù)類型來代替。(Java虛擬機(jī)會把boolean數(shù)組元素中的true采用1來表示匈棘,false采用0來表示丧慈,當(dāng)Java編譯器把Java語言中的boolean類型值映射為Java虛擬機(jī)的int類型值時,也必須用上述表示方式)
引用類型與值

Java虛擬機(jī)中有三種引用類型:類類型主卫、數(shù)組類型和接口類型逃默。它們分別指向動態(tài)創(chuàng)建的類實例、數(shù)組實例和某個接口的類實例或數(shù)組實例簇搅。
數(shù)組類型最外面那一維元素的類型叫做數(shù)組類型的組件類型完域。一個數(shù)組的組件類型也可以是數(shù)組。從任意一個數(shù)組開始馍资,如果發(fā)現(xiàn)其組件類型也是數(shù)組類型筒主,那就繼續(xù)取這個小數(shù)組的組件類型,不斷執(zhí)行這樣的操作鸟蟹,最終一定可以遇到組件類型不是數(shù)組的情況乌妙,這時就把這種類型成為本數(shù)組的元素類型。數(shù)組的元素類型必須是原生類型建钥、類類型或者接口類型之一藤韵。
在引用類型的值中還有一個特殊的值:null,當(dāng)一個引用不指向任何對象的時候,它的值就用null來表示熊经。一個為null的引用泽艘,起初并不具備任何實際的運行期類型,但是它可轉(zhuǎn)型為任意的引用類型镐依。引用類型的默認(rèn)值就是null匹涮。Java虛擬機(jī)規(guī)范并沒有規(guī)定null在虛擬機(jī)實現(xiàn)中應(yīng)當(dāng)怎樣用編碼來表示。

運行時數(shù)據(jù)區(qū)域

由于《深入理解Java虛擬機(jī)》一書中對Java虛擬機(jī)運行時數(shù)據(jù)區(qū)域介紹得更詳細(xì)槐壳,所以這里直接貼上以前的讀書筆記然低。
深入理解Java虛擬機(jī)---自動內(nèi)存管理機(jī)制

棧幀結(jié)構(gòu)

這里再特別回顧一下幾個不太熟悉的知識點:

  • 棧幀:棧幀是用來存儲數(shù)據(jù)和部分過程結(jié)果的數(shù)據(jù)結(jié)構(gòu),同時也用來處理動態(tài)鏈接、方法返回值和異常分派雳攘。棧幀又是存儲在棧中(包括Java虛擬機(jī)棧和本地方法棧)带兜,它隨著方法調(diào)用而創(chuàng)建,隨著方法結(jié)束而銷毀吨灭,其實也就是一個方法執(zhí)行的過程也對應(yīng)著棧幀的入棧和出棧的過程刚照。無論方法是正常完成還是異常完成(拋出了在方法內(nèi)未被捕獲的異常)都算作方法結(jié)束。棧幀的存儲空間由創(chuàng)建它的線程分配在Java虛擬機(jī)棧之中喧兄,每一個棧幀都有自己的本地變量表无畔、操作數(shù)棧和指向當(dāng)前方法所屬的類的運行時常量池的引用。
    本地變量表和操作數(shù)棧的容量在編譯期確定繁莹,并通過相關(guān)方法的code屬性保存及提供給棧幀使用檩互。因此,棧幀數(shù)據(jù)結(jié)構(gòu)的大小僅僅取決于Java虛擬機(jī)的實現(xiàn)咨演。實現(xiàn)者可以在調(diào)用方法的時候給它們分配內(nèi)存闸昨。
    在某條線程執(zhí)行的過程中的某個時間點,只有目前正在執(zhí)行的那個方法的棧幀是活動的薄风。這個棧幀稱為當(dāng)前棧幀饵较,這個棧幀對應(yīng)的方法稱為當(dāng)前方法,定義這個方法的類稱作當(dāng)前類遭赂。對局部變量表和操作數(shù)棧的各種操作循诉,通常都是值對當(dāng)前棧幀的局部變量表和操作數(shù)棧所進(jìn)行的操作。
    如果當(dāng)前方法調(diào)用了其他方法撇他,或者當(dāng)前方法執(zhí)行結(jié)束茄猫,那這個方法的棧幀就不再是當(dāng)前棧幀了。調(diào)用新方法時困肩,新的棧幀也會隨之而創(chuàng)建划纽,并且會隨著程序控制權(quán)移交到新方法而成為新的當(dāng)前棧幀。方法返回之際锌畸,當(dāng)前棧幀會傳回此方法給前一個棧幀勇劣,然后虛擬機(jī)會丟棄當(dāng)前棧幀,使得前一個棧幀重新成為當(dāng)前棧幀潭枣。
    需要特別注意的是比默,棧幀是線程本地私有的數(shù)據(jù),不可能在一個棧幀之中引用另外一個線程的棧幀盆犁。

  • 局部變量表
    每個棧幀內(nèi)部都包含一組稱為局部變量表的變量列表命咐。棧幀中局部變量表的長度由編譯器決定,并卻存儲于類或接口的二進(jìn)制表示之中谐岁,即通過方法的code屬性保存及提供給棧幀使用醋奠。
    一個局部變量可以保存一個類型為boolean瓮下、byte、char钝域、short、int锭魔、float例证、reference或returnAddress的數(shù)據(jù)。兩個局部變量可以保存一個類型為long或double的數(shù)據(jù)迷捧。
    局部變量使用索引來進(jìn)行定位訪問织咧。首個局部變量的索引值為0。局部變量的索引值是個整數(shù)漠秋,它大于等于0笙蒙,且小于局部變量表的長度。Java虛擬機(jī)使用局部變量表來完成方法調(diào)用時的參數(shù)傳遞庆锦。當(dāng)調(diào)用類方法時捅位,它的參數(shù)將會依次傳遞到局部變量表中從0開始的連續(xù)位置上。當(dāng)調(diào)用實例方法時搂抒,第0個局部變量一定用來存儲該實例方法所在對象的引用(即Java語言中的this關(guān)鍵字)艇搀。后續(xù)其他參數(shù)將會傳遞至局部變量表中從1開始的連續(xù)位置上。

  • 操作數(shù)棧:每個棧幀內(nèi)部都包含一個稱為操作數(shù)棧的后進(jìn)后出棧求晶。棧幀中操作數(shù)棧的最大深度由編譯期決定焰雕,并且通過方法的code屬性保存及提供給棧幀使用。棧幀剛創(chuàng)建的時候操作數(shù)棧是空的芳杏。Java虛擬機(jī)提供一些字節(jié)碼指令來從局部變量表或者對象實例的字段中復(fù)制常量或變量值到操作數(shù)棧中矩屁,也提供了一些指令用于從操作數(shù)棧取走數(shù)據(jù)、操作數(shù)據(jù)以及把操作結(jié)果重新入棧爵赵。在調(diào)用方法時吝秕,操作數(shù)棧也用來準(zhǔn)備調(diào)用方法的參數(shù)以及接收方法返回結(jié)果。例如iadd字節(jié)碼指令的作用是將兩個int類型的數(shù)值相加亚再,它要求在執(zhí)行之前操作數(shù)棧的棧頂已經(jīng)存在兩個由前面的其他指令所放入的int類型數(shù)值郭膛。在執(zhí)行iadd指令時,兩個int類型數(shù)值出棧氛悬,相加求和之后求和結(jié)果重新入棧则剃。操作數(shù)棧的每個位置上可以保存一個Java虛擬機(jī)中定義的數(shù)據(jù)類型的值,包括long和double類型如捅。在任意時刻棍现,操作數(shù)棧都會有一個確定的棧深度,一個long或者double類型的數(shù)據(jù)會占用兩個單位的棧深度镜遣,其他數(shù)據(jù)類型則會占用一個單位的棧深度己肮。

  • 動態(tài)鏈接
    每個棧幀內(nèi)部都包含一個指向當(dāng)前方法所在類型的運行時常量池的引用士袄,以便對當(dāng)前方法的代碼實現(xiàn)動態(tài)鏈接。在class文件里面一個方法若要調(diào)用其他方法谎僻,或者訪問成員變量娄柳,則需要通過符號引用來表示。動態(tài)鏈接的作用就是將這些符號引用所表示的方法轉(zhuǎn)換為對實際方法的直接引用艘绍。類加載的過程中將要解析尚未被解析的符號引用赤拒,并且將對變量的訪問轉(zhuǎn)化為變量在程序運行時,位于存儲結(jié)構(gòu)中的正確偏移量诱鞠。由于對其他類中的方法和變量進(jìn)行了晚期綁定挎挖,所以即便那些類發(fā)生變化,也不會影響調(diào)用它們的方法航夺。

對象的表示

Java虛擬機(jī)規(guī)范不強(qiáng)制規(guī)定對象的內(nèi)部結(jié)構(gòu)應(yīng)該如何表示蕉朵。在具體實現(xiàn)中,一般有兩種對象的訪問方式阳掐,分別是通過句柄訪問對象以及通過直接指針訪問對象始衅。
在之前的博客里我也總結(jié)過這兩種對象訪問方式的優(yōu)劣,想要了解的同學(xué)可以參考這篇博客

特殊方法

在Java虛擬機(jī)層面锚烦,Java編程語言的構(gòu)造器是以一個名為<init>的特殊實例初始方法的形式出現(xiàn)的觅闽。<init>這個方法名稱是由編譯器命名的,因為它并非一個合法的Java方法名字涮俄,不可能通過程序編碼的方式實現(xiàn)蛉拙。實例初始化方法的初始化期間,通過Java虛擬機(jī)的invokespecial指令來調(diào)用彻亲,而且只能在尚未初始化的實力上調(diào)用該指令孕锄。構(gòu)造器的訪問權(quán)限也會約束由該構(gòu)造器所衍生出來的實例初始化方法。

一個類或者接口最多可以包含不超過一個類或接口的初始化方法苞尝,類或接口就是通過這個方法完成初始化的畸肆。這個方法是一個不包含參數(shù)的、返回類型為void的方法宙址,名為<clinit>轴脐。

在class文件中把其他方法命名為<clinit>是沒有意義的,這些方法并不是類或接口的初始化方法抡砂,它們既不能被字節(jié)碼指令調(diào)用大咱,也不會被虛擬機(jī)自己調(diào)用。當(dāng)class文件的版本號不小于51.0時注益,<clinit>方法想要成為類或接口的初始化方法碴巾,必須設(shè)置ACC_STATIC標(biāo)志。

異常

Java虛擬機(jī)里面的異常使用Throwable或其子類的實例來表示丑搔,拋異常的本質(zhì)實際上是程序控制權(quán)轉(zhuǎn)移的一種即時的厦瓢、非局部的轉(zhuǎn)換---從異常拋出的地方轉(zhuǎn)換至異常處理的地方提揍。
絕大多數(shù)異常的產(chǎn)生都是由于當(dāng)前線程執(zhí)行的某個操作所導(dǎo)致的,這種可以稱為同步異常煮仇。與之相對劳跃,異步異常可以在程序執(zhí)行過程中隨時發(fā)生浙垫。Java虛擬機(jī)中異常的出現(xiàn)總是由下面三種原因之一導(dǎo)致的:

  • athrow字節(jié)碼指令被執(zhí)行售碳;
  • 虛擬機(jī)同步檢測到程序發(fā)生了非正常的執(zhí)行情況,這時異常必將緊接著發(fā)生在非正常執(zhí)行情況的字節(jié)碼指令之后拋出绞呈,而不會在執(zhí)行程序的過程中隨時拋出。例如:程序所執(zhí)行的操作可能會引發(fā)異常---當(dāng)字節(jié)碼指令所蘊含的操作違反了Java語言的語義间景,如訪問一個超出數(shù)組邊界范圍的元素佃声,或者是當(dāng)程序在加載或者連接時出現(xiàn)錯誤;還有一種異常是使用某些資源的時候產(chǎn)生資源限制倘要,比如說使用了太多的內(nèi)存圾亏。
  • 由于以下原因,導(dǎo)致了異步異常的發(fā)生: 調(diào)用了Thread或者ThreadGroup的stop方法封拧;Java虛擬機(jī)實現(xiàn)發(fā)生了內(nèi)部錯誤志鹃。

當(dāng)某個線程調(diào)用了stop方法時,將會影響到其他線程泽西,或者在特定線程組中的所有線程曹铃。因為stop方法的執(zhí)行常常會導(dǎo)致出現(xiàn)數(shù)據(jù)不一致的情況,這時候其他線程中出現(xiàn)的異常就是異步異常捧杉,因為這些異成录可能出現(xiàn)在線程執(zhí)行過程中的任何位置。虛擬機(jī)的內(nèi)部錯誤也被認(rèn)為是一種異步異常味抖。

虛擬機(jī)錯誤
  • InternalError:實現(xiàn)虛擬機(jī)的軟件錯誤评甜、底層主機(jī)系統(tǒng)的軟件錯誤及硬件錯誤都會導(dǎo)致Java虛擬機(jī)出現(xiàn)內(nèi)部錯誤,InternalError是一個異步異常仔涩,它可能出現(xiàn)在程序中的任何位置忍坷;
  • OutOfMemoryError:當(dāng)Java虛擬機(jī)實現(xiàn)耗盡了所有虛擬或物理內(nèi)存,并且內(nèi)存自動管理子系統(tǒng)無法回收到創(chuàng)建新對象所需的足夠內(nèi)存空間時熔脂,虛擬機(jī)將拋出OutOfMemoryError佩研。
  • StackOverflowError:當(dāng)Java虛擬機(jī)實現(xiàn)耗盡了線程全部的棧空間時锤悄,虛擬機(jī)將會拋出StackOverflowError韧骗。
  • UnKnownError:當(dāng)某種錯誤或異常出現(xiàn),但虛擬機(jī)實現(xiàn)又無法確定它具體是哪種異沉憔郏或錯誤袍暴,將會拋出UnKnownError些侍。

由于通常虛擬機(jī)會對代碼進(jìn)行優(yōu)化,例如指令重排政模。那么在異常發(fā)生的時候岗宣,有一些在異常出現(xiàn)位置之后的代碼可能已經(jīng)執(zhí)行了,那這些優(yōu)化過的代碼必須保證它們提前執(zhí)行所產(chǎn)生的影響對用戶程序來說是不可見的淋样。

由Java虛擬機(jī)執(zhí)行的每個方法都會配有零到多個異常處理器耗式。異常處理器描述了其在方法代碼中的有效作用范圍(通過字節(jié)碼偏移量范圍來描述)、能處理的異常類型以及處理異常的代碼所在的位置趁猴。要判斷某個異常處理器是否可以處理某個具體的異常刊咳,需要同時檢查一場出現(xiàn)的位置是否在異常處理的有效作用范圍內(nèi),以及出現(xiàn)的異常是否是異常處理器聲明可以處理的異常類型或其子類型儡司。當(dāng)拋出異常時娱挨,Java虛擬機(jī)搜索當(dāng)前方法包含的各個異常處理器,如果能找到可以處理該異常的異常處理器捕犬,則將代碼控制權(quán)轉(zhuǎn)向異常處理器中描述的處理異常的分支之中跷坝。

下面我舉一個簡單例子

方法中捕捉了ArithmeticException
上面這個類對應(yīng)的字節(jié)碼

上面的字節(jié)碼比較容易理解,首先簡單介紹一下main方法中各條字節(jié)碼指令所代表的意思:

  • 0 : 將int類型的常量i壓入操作數(shù)棧中碉碉,iconst_0后面跟的那個0代表常量的值為0柴钻;
  • 1:將一個int類型數(shù)據(jù)由保存到本地變量表,istort_1后面的1代表的是指向當(dāng)前棧幀中局部變量表的索引值垢粮;
  • 2:將一個值為3的int類型的常量壓入操作數(shù)棧中贴届。在這里指的是被除數(shù)3;
  • 3:從局部變量表加載一個int類型值到操作數(shù)棧中蜡吧,這里指的是除數(shù)i粱腻;
  • 4:對兩個int類型的數(shù)據(jù)做除法;
  • 5:將兩數(shù)相除之后所得到的int類型數(shù)據(jù)保存到本地變量表斩跌;
  • 6:假如沒有發(fā)生異常的話绍些,那么執(zhí)行完goto到第14條語句,函數(shù)正常返回耀鸦;
  • 9:假如發(fā)生了除零異常柬批,就執(zhí)行這條指令,將異常對象保存到局部變量表中袖订;
  • 10:從局部變量表中加載剛才的那個異常對象到操作數(shù)中氮帐;
  • 11:調(diào)用異常對象的printStackTrace方法
  • 14:不管是正常完成還是異常完成,最終都會返回洛姑。

在字節(jié)碼下方可以看到一個Exception table上沐。那么它是什么東西呢?其實我們很容易能夠理解它就是異常表楞艾,也就是前面我們提過的異常處理器参咙。我們可以明顯地觀察出龄广,其實try-catch代碼塊編譯之后似乎沒有生成任何指令。那么Java語言中的try-catch放到字節(jié)碼當(dāng)中對應(yīng)什么東西呢蕴侧?其實就是對應(yīng)這個異常處理器择同。下面我們來解讀一下異常處理器:

異常處理器

在try語句塊的執(zhí)行過程中如果沒有拋出異常,那么這個異常處理器不會起作用净宵。異常處理器的作用范圍是從字節(jié)碼的第2行到第6行敲才,也就是from-to標(biāo)明的范圍。假如編譯好的代碼里面第2~6句之間有一個類型為java.lang.ArithmeticException的異常實例被拋出择葡,那么操作將轉(zhuǎn)移至第9句繼續(xù)執(zhí)行紧武,即進(jìn)入catch語句塊的實踐步驟。假如說拋出的異常不是ArithmeticException實例敏储,那么異常處理器就不能處理該異常脏里,這個異常將返回給上一級的調(diào)用者。

那假如try語句塊包含多個catch語句塊虹曙,在編譯好的代碼中會出現(xiàn)什么樣的結(jié)果呢?

在Java代碼中增加一個catch代碼塊
上述代碼編譯之后的字節(jié)碼

如果給定的try語句塊包含多個catch語句塊番舆,那么在編譯好的代碼中酝碳,多個catch語句塊的內(nèi)容將會連續(xù)排列,在異常表中也會有對應(yīng)的連續(xù)排列的成員恨狈,它們的排列順序和源碼中catch語句塊的出現(xiàn)順序一致疏哗。main方法在執(zhí)行時,如果try語句塊中拋出了一個異常禾怠,這個異常將會被多個catch語句塊捕獲返奉。假如第一個catch不能捕獲異常(當(dāng)然這里的第一個catch語句塊肯定是能處理ArithmeticException,我只是舉個例子),那么異常將交由第二個異常處理器來進(jìn)行處理吗氏,這很容易理解芽偏。因為我在第二個catch語句塊中選擇的是將捕獲的異常拋出,所以在字節(jié)碼的第26行可以看到有一個athrow指令弦讽,在前面的學(xué)習(xí)當(dāng)中我們知道它是拋出異常的意思污尉,其實也就是對應(yīng)著Java代碼中的throw new Exception()。在這里往产,我還要順便介紹一下Java創(chuàng)建一個對象的代碼在編譯之后會產(chǎn)生怎樣的字節(jié)碼被碗。

其實,剛才我所說的throw new Exception()對應(yīng)athrow字節(jié)碼指令只說對了一半仿村,它在編譯之后不僅僅只產(chǎn)生athrow這一條字節(jié)碼指令锐朴。因為它還對應(yīng)著一個操作,也就是new一個Exception對象蔼囊。Java語言實例化一個Exception對象將會產(chǎn)生三條字節(jié)碼指令焚志,即上圖中19衣迷,22,23三行:

實例化Exception對象對應(yīng)的字節(jié)碼

為什么會有三條指令呢娩嚼?dup是做什么的蘑险?我們下面一起來學(xué)習(xí)一下
由于討論的是創(chuàng)建對象,所以在代碼throw new Exception()中我們不看throw岳悟,只看new Exception()這一部分代碼佃迄。

new Exception()表達(dá)式的作用是:

  • 創(chuàng)建并默認(rèn)初始化一個Exception對象;
  • 調(diào)用Exceptioon類的signature為<init>()V的構(gòu)造器贵少;
  • 表達(dá)式的值為一個指向這個對象的引用

對應(yīng)字節(jié)碼呵俏,我們可以看到:

  • new Exception()對應(yīng)上面的1
  • invokespecial Exception.<init>()V對應(yīng)上面的2
  • 那么3是怎么來的?

回歸到字節(jié)碼滔灶,我們可以看到new字節(jié)碼指令的作用是創(chuàng)建指定類型的對象實例普碎、對其進(jìn)行默認(rèn)初始化,并將指向該實例的一個引用壓入操作數(shù)棧頂录平;
然后因為invokespecial會消耗操作數(shù)棧頂?shù)囊米鳛閭鹘o構(gòu)造器的"this"參數(shù)麻车,所以如果我們希望在invokespecial調(diào)用后在操作數(shù)棧頂還維持有一個指向新建對象的引用,就得在invokespecial之前先“復(fù)制”一份引用----這就是dup的來源斗这。
以上动猬,就是對創(chuàng)建一個對象編譯之后產(chǎn)生的字節(jié)碼的解釋

編譯finally語句塊

剛才我們介紹了異常處理在字節(jié)碼層面的細(xì)節(jié),但是我們還需要注意的是----由于finally能夠保證不管發(fā)生任何情況表箭,都能夠執(zhí)行語句塊中的代碼赁咙,所以在日常編碼過程中我們在可能發(fā)生異常的地方(或者是不會發(fā)生異常的地方)經(jīng)常使用finally來釋放某些資源。
下面我們從虛擬機(jī)層面來看看如何保證finally語句塊中的代碼一定會執(zhí)行

增加了finally語句塊
上述Java代碼編譯之后產(chǎn)生的字節(jié)碼

可以看到免钻,其實編譯器是通過在每個分支后面增加冗余代碼的形式來保證finally語句塊中的代碼一定會被執(zhí)行彼水。這里和書上講的有點出入,書上在講解這一塊的時候還是用jsr极舔、jsr_w凤覆、ret等程序控制轉(zhuǎn)移指令來解釋的,但是javac在很早之前就不再為finally語句生成jsr和ret指令了拆魏。

如果程序在try語句塊中執(zhí)行了return叛赚,那么代碼的行為如下:

  • 如果有返回值,將返回值保存在局部變量表稽揭;
  • 執(zhí)行跟在后面的冗余finally語句塊中的代碼俺附;
  • 在finally執(zhí)行完之后,將事先保存在局部變量表中的返回值壓入操作數(shù)棧中之后返回溪掀。

如果在try語句中拋出異常事镣,那么代碼的行為如下:

  • 將異常保存在局部變量表中
  • 執(zhí)行finally語句塊中的代碼
  • 在執(zhí)行完finally語句塊中的代碼后,重新拋出這個事先保存好的異常。

同步

Java虛擬機(jī)中的同步(synchronization)使用monitor的進(jìn)入和退出來實現(xiàn)的璃哟。無論顯式同步(有明確的monitorenter和monitorexit指令)氛琢,還是隱式同步(依賴方法調(diào)用和返回指令實現(xiàn))都是如此。
在Java語言中随闪,同步用得最多的地方可能是經(jīng)synchronized所修飾的同步方法阳似。同步方法并不是用monitorenter和monitorexit來實現(xiàn)的,而是由方法調(diào)用指令讀取運行時常量池中方法的ACC_SYNCHRONIZED標(biāo)志來隱式實現(xiàn)的铐伴。
monitorenter和monitorexit指令用于編譯同步語句塊

同步語句塊
編譯后的代碼

編譯器必須確保無論方法以何種方式完成(正常結(jié)束或者是異常結(jié)束)撮奏,方法中調(diào)用過的每條monitorenter指令都必須有對應(yīng)的monitorexit指令得到執(zhí)行。為了確保在方法異常完成時当宴,monitorenter和monitorexit指令依然可以正確配對執(zhí)行畜吊,編譯器會自動生成一個異常處理器,這個異常處理器宣稱自己可以處理所有異常户矢,它的代碼用來執(zhí)行monitorexit指令玲献。

類加載

之前寫過關(guān)于類加載的博客,這里就不贅述了梯浪。下面把之前關(guān)于類加載的博客貼出來捌年。
深入了解Java虛擬機(jī)---虛擬機(jī)類加載機(jī)制

寫在后面:由于本書介紹的是Java虛擬機(jī)規(guī)范,所以使用了大篇幅的內(nèi)容來講class文件格式以及各種Java虛擬機(jī)代碼約束挂洛、Java虛擬機(jī)指令集等等礼预。我硬著頭皮看了一遍,也不指望能夠記住什么抹锄。。因為東西太多了荠藤,根本記不住伙单。其實個人覺得虛擬機(jī)規(guī)范這種東西就像字典一樣,不可能完全記住哈肖,只需要在遇到問題的時候知道可能是什么地方的問題吻育,知道在哪里能找到答案就行了。話說回來讀完全書之后感覺收獲還是挺多的淤井,尤其是之前很多知識點我都只是停留在對Java語言上的理解布疼,沒有深入到Java虛擬機(jī)字節(jié)碼這個層面去探究,所以有些東西是知其然不知其所以然币狠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末游两,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子漩绵,更是在濱河造成了極大的恐慌贱案,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件止吐,死亡現(xiàn)場離奇詭異宝踪,居然都是意外死亡侨糟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門瘩燥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秕重,“玉大人,你說我怎么就攤上這事厉膀∪茉牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵站蝠,是天一觀的道長汰具。 經(jīng)常有香客問我,道長菱魔,這世上最難降的妖魔是什么留荔? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮澜倦,結(jié)果婚禮上聚蝶,老公的妹妹穿的比我還像新娘。我一直安慰自己藻治,他們只是感情好碘勉,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桩卵,像睡著了一般验靡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雏节,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天胜嗓,我揣著相機(jī)與錄音,去河邊找鬼钩乍。 笑死辞州,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寥粹。 我是一名探鬼主播变过,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涝涤!你這毒婦竟也來了媚狰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤阔拳,失蹤者是張志新(化名)和其女友劉穎哈雏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡裳瘪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年土浸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彭羹。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡黄伊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出派殷,到底是詐尸還是另有隱情还最,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布毡惜,位于F島的核電站拓轻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏经伙。R本人自食惡果不足惜扶叉,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帕膜。 院中可真熱鬧枣氧,春花似錦、人聲如沸垮刹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荒典。三九已至酪劫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間寺董,已是汗流浹背覆糟。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留螃征,地道東北人搪桂。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓透敌,卻偏偏與公主長得像盯滚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子酗电,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一魄藕、運行時數(shù)據(jù)區(qū)域 Java虛擬機(jī)管理的內(nèi)存包括幾個運行時數(shù)據(jù)內(nèi)存:方法區(qū)、虛擬機(jī)棧撵术、本地方法棧背率、堆、程序計數(shù)器,...
    加油小杜閱讀 1,511評論 1 15
  • Java 虛擬機(jī)屏蔽了與具體操作系統(tǒng)平臺相關(guān)的信息,使得 Java 語言編譯程序只需生成在 Java 虛擬機(jī)上運行...
    尋夢的尕柳閱讀 859評論 0 11
  • JAVA虛擬機(jī)的生命周期 一個運行時的Java虛擬機(jī)實例的天職是:負(fù)責(zé)運行一個java程序寝姿。當(dāng)啟動一個Java程序...
    Solang閱讀 1,168評論 0 19
  • 在現(xiàn)實中交排,經(jīng)常會有人在痛心,我與她是真心相愛饵筑,我喜歡她埃篓,她也有表示她喜歡我,可為什么根资,我們還是感覺不到在一起...
    殤斷閱讀 249評論 1 2
  • 寫作的好處數(shù)不勝數(shù)玄帕。今天又發(fā)現(xiàn)他的另一個奇特功效——克服焦慮部脚。 最近,心中產(chǎn)生了一種莫可名狀的焦慮情緒裤纹。這種奇怪的...
    安和然閱讀 553評論 0 5