Java的技術(shù)體系包括
- 支持Java程序運(yùn)行的虛擬機(jī)(JVM)
- 提供接口支持的Java API
- Java 編程語(yǔ)言
- 第三方Java框架(如Spring等)
代碼編譯的結(jié)果從本地機(jī)器碼轉(zhuǎn)變?yōu)樽止?jié)碼谭贪,是存儲(chǔ)格式發(fā)展的一小步俯画,確實(shí)編程語(yǔ)言的一大步。
java代碼經(jīng)過(guò)編譯器編譯后玉罐,類(lèi)中方法體內(nèi)的代碼邏輯會(huì)被編譯為一行行虛擬機(jī)能夠識(shí)別的指令,而java虛擬機(jī)規(guī)范中通過(guò)一個(gè)字節(jié)來(lái)存儲(chǔ)所有的虛擬機(jī)指令明也,因此我們將這種指令集稱之為字節(jié)碼指令铭段。
字節(jié)碼指令的含義
字節(jié)碼指令是指,由一個(gè)字節(jié)長(zhǎng)度來(lái)表示胧华,代表著特定含義的數(shù)字(稱之為操作碼)所構(gòu)成的指令寄症,其后有可能跟隨一個(gè)或者多個(gè)此命令所需要的參數(shù)(稱之為操作數(shù))宙彪。大多數(shù)的字節(jié)碼指令都是不包含操作數(shù)的,只存在一個(gè)操作碼有巧。
字節(jié)碼指令的優(yōu)勢(shì)在于释漆,由于不需要操作數(shù)長(zhǎng)度對(duì)齊,所以可以節(jié)省很多用于操作數(shù)對(duì)齊而帶來(lái)的填充和間隔符號(hào)所占用的空間篮迎。同時(shí)一個(gè)字節(jié)的長(zhǎng)度來(lái)代表操作碼男图,從設(shè)計(jì)的初衷我們就能看出視為了盡可能獲得短小的編譯代碼。而這樣的設(shè)計(jì)初衷也是由java語(yǔ)言為了盡可能追求在小數(shù)據(jù)量和高傳輸效率的場(chǎng)景中發(fā)揮優(yōu)勢(shì)所決定的甜橱。
它的劣勢(shì)同樣十分明顯享言,由于一個(gè)字節(jié)所能代表的取值范圍為0~255,這就意味著操作碼最多只能有256個(gè)渗鬼。同時(shí)由于沒(méi)有操作數(shù)長(zhǎng)度對(duì)齊览露,當(dāng)操作碼需要處理的數(shù)據(jù)大于一個(gè)字節(jié)的長(zhǎng)度的時(shí)候,比如要存儲(chǔ)一個(gè)16位長(zhǎng)度的數(shù)據(jù)譬胎,那需要兩個(gè)字節(jié)來(lái)存儲(chǔ)差牛,并且必須在運(yùn)行時(shí)通過(guò)某種規(guī)則還原出原始的數(shù)據(jù)。這樣的操作會(huì)導(dǎo)致字節(jié)碼在執(zhí)行的時(shí)候必然會(huì)損耗一些性能堰乔。
分類(lèi)
字節(jié)碼指令按照用途偏化,大致可以分為9類(lèi)
- 加載和存儲(chǔ)指令
- 運(yùn)算指令
- 類(lèi)型轉(zhuǎn)換指令
- 對(duì)象創(chuàng)建與訪問(wèn)指令
- 操作數(shù)棧管理指令
- 控制轉(zhuǎn)移指令
- 方法調(diào)用和返回指令
- 異常處理指令
- 同步指令
加載和存儲(chǔ)指令
加載和存儲(chǔ)指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來(lái)回傳輸,這些指令包括如下
- 將一個(gè)局部變量加載到操作數(shù)棧:iload, iload_<n>, lload, lload<n>, fload, fload_<n>, dload, dload_<n>, aload, aload_<n>
- 將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore, istore_<n>, lstore, lstore_<n>, fstore, fstore_<n>, dstore, dstore_<n>, astore, astore_<n>
- 將一個(gè)常量加載到操作數(shù)棧:bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_ml, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
- 擴(kuò)充局部變量表的訪問(wèn)索引的指令:wide
運(yùn)算指令
運(yùn)算指令用于將兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定的運(yùn)算镐侯,并把結(jié)果重新存入到操作數(shù)棧頂侦讨。算數(shù)指令大體可以分為兩種:對(duì)整型數(shù)據(jù)進(jìn)行運(yùn)算的指令,和對(duì)浮點(diǎn)型數(shù)據(jù)進(jìn)行運(yùn)算的指令苟翻。
- 加法指令:iadd, ladd, fadd, dadd
- 減法指令:isub, lsub, fsub, dsub
- 乘法指令:imul, lmul, fmul, dmul
- 除法指令:idiv, ldiv, fdiv, ddiv
- 求余指令:irem, lrem, frem, drem
- 取反指令:ineg, lneg, fneg, dneg
- 位移指令:ishl, ishr, iushr, lshl, lshr, lushr
- 按位或指令:ior, lor
- 按位與指令:iand, land
- 按位異或指令:ixor, lxor
- 局部變量自增指令:iinc
- 比較指令:dcmpg, dcmpl, fcmpg, fcmpl, lcmp
虛擬機(jī)規(guī)范中規(guī)定韵卤,在運(yùn)算指令時(shí),只有在除法指令或者求余指令中當(dāng)除數(shù)為0的時(shí)候崇猫,會(huì)拋出ArithmeticException沈条,其余任何場(chǎng)景即使出現(xiàn)溢出,也不會(huì)產(chǎn)生任何運(yùn)行時(shí)異常
類(lèi)型轉(zhuǎn)換指令
類(lèi)型轉(zhuǎn)換指令可以將兩種不同數(shù)值類(lèi)進(jìn)行相互轉(zhuǎn)換诅炉。java虛擬機(jī)直接支持以下類(lèi)型的寬化類(lèi)型轉(zhuǎn)換
- int -> long float double
- long -> folat double
- float -> double
對(duì)于處理窄化類(lèi)型轉(zhuǎn)換時(shí)蜡歹,必須顯式地使用轉(zhuǎn)換指令來(lái)完成。這些指令包括:i2b, i2c, i2s, f2i, f2l, d2i, d2l, d2f涕烧。數(shù)據(jù)類(lèi)型窄化處理可能會(huì)出現(xiàn)溢出和精度丟失等問(wèn)題月而,但是java虛擬機(jī)規(guī)范中規(guī)定,數(shù)值窄化處理不會(huì)拋出任何運(yùn)行時(shí)異常议纯。
對(duì)象創(chuàng)建與訪問(wèn)指令
雖然在java語(yǔ)言層面父款,類(lèi)實(shí)例和數(shù)組都是對(duì)象,但是虛擬機(jī)對(duì)類(lèi)實(shí)例和數(shù)組的創(chuàng)建和訪問(wèn)使用的是不同的指令,這是因?yàn)樗麄兊膭?chuàng)建過(guò)程是不同的铛漓。指令如下
- 創(chuàng)建類(lèi)實(shí)例的指令:new
- 創(chuàng)建數(shù)組是的指令:newarray, anewarray,multianewarray
- 訪問(wèn)類(lèi)變量和實(shí)例變量:getfield, putfield, getstatic, putstatic
- 把一個(gè)數(shù)組元素加載到操作數(shù)棧的指令:baload, caload, saload, iaload, laload, faload, daload, aaload
- 把一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中的指令:bastore, castore, sastore, iastore, fastore, dastore, aastore
- 取數(shù)組長(zhǎng)度指令:arraylength
- 檢查類(lèi)實(shí)例類(lèi)型的指令:instanceof, checkcast
操作數(shù)棧管理指令
- 將操作數(shù)棧的棧頂一個(gè)或者兩個(gè)元素出棧:pop, pop2
- 復(fù)制棧頂一個(gè)或者兩個(gè)數(shù)值溯香,并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup, dup2, dup_1, dup2_1, dup_2, dup2_2
- 將棧最頂端的兩個(gè)數(shù)值互換:swap
控制轉(zhuǎn)移指令
控制轉(zhuǎn)移指令可以讓虛擬機(jī)有條件或者無(wú)條件地從指定位置繼續(xù)執(zhí)行程序鲫构。指令如下
- 條件分支:ifeq, iflt, ifle, ifgt, ifge, ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmpgt, if_icacmpeq, if_acmpne
- 復(fù)合條件分支:tableswitch, lookupswitch
- 無(wú)條件分支:goto, goto_w, jsr, jsr_w, ret
方法調(diào)用和返回指令
- invokevirtual, 用于調(diào)用對(duì)象的實(shí)例方法浓恶,根據(jù)對(duì)象的實(shí)際類(lèi)型進(jìn)行分派
- invokeinterface, 用于調(diào)用接口方法,它會(huì)在運(yùn)行時(shí)搜索一個(gè)實(shí)現(xiàn)了這個(gè)接口方法的對(duì)象结笨,找出適合的方法進(jìn)行調(diào)用
- invokespecial, 用于調(diào)用一些需要特殊處理的實(shí)例方法包晰,包括實(shí)例初始化方法、私有方法炕吸、父類(lèi)方法
- invokestatic, 用于調(diào)用類(lèi)方法(static方法)
- invokedynamic, 用于在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法伐憾,并執(zhí)行該方法。
- 方法返回指令:return, ireturn, lreturn, freturn, dreturn, areturn
異常處理指令
java程序中赫模,顯式拋出異常的操作都是由athrow指令來(lái)實(shí)現(xiàn)树肃。除此之外,虛擬機(jī)規(guī)范還規(guī)定了許多運(yùn)行時(shí)異常會(huì)由虛擬機(jī)自動(dòng)拋出瀑罗。
同步指令
java虛擬機(jī)可以支持方法級(jí)的同步和方法內(nèi)部一段指令序列的同步胸嘴,都是使用管程(Monitor)來(lái)支持。方法級(jí)的同步是隱式的斩祭,無(wú)需通過(guò)字節(jié)碼指令來(lái)控制劣像,因?yàn)橥ㄟ^(guò)方法常量池的方法表結(jié)構(gòu)中的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志可以得知一個(gè)方法是否聲明為同步方法。方法內(nèi)部的同步則是通過(guò) monitorenter 和 monitorexit 兩條指令來(lái)完成摧玫。編譯器需要確保耳奕,方法中調(diào)用過(guò)的每條 monitorenter 指令都必須執(zhí)行其對(duì)應(yīng)的 monitorexit指令。
總結(jié)
Java虛擬機(jī)規(guī)范規(guī)定了Java虛擬機(jī)應(yīng)共同遵守的存儲(chǔ)格式:Class文件格式 和 字節(jié)碼指令集诬像。Class文件結(jié)構(gòu)從虛擬機(jī)規(guī)范發(fā)布以來(lái)屋群,java的技術(shù)體系發(fā)生了很多很多的變化,包括語(yǔ)言坏挠,API等有了極大的發(fā)展和很多的變化谓晌。但Class文件結(jié)構(gòu)一直處于比較穩(wěn)定的狀態(tài),它的主體結(jié)構(gòu)癞揉、字節(jié)碼指令都幾乎沒(méi)有什么大的變化纸肉。這也說(shuō)明了反應(yīng)出虛擬機(jī)的具體實(shí)現(xiàn)和上層的使用之間的耦合性很低,靈活性很大喊熟。
虛擬機(jī)加類(lèi)加載機(jī)制的前三篇文章柏肪,詳細(xì)闡述了Class文件的組成部分,每部分的含義芥牌,結(jié)構(gòu)烦味,以及使用方法。因?yàn)樗翘摂M機(jī)執(zhí)行引擎的數(shù)據(jù)入口,也是Java技術(shù)體系最重要的基礎(chǔ)構(gòu)成之一谬俄。
Class文件格式所具備的平臺(tái)中立柏靶、緊湊、穩(wěn)定溃论、可擴(kuò)展的特點(diǎn)屎蜓,是Java技術(shù)體系能夠?qū)崿F(xiàn)平臺(tái)無(wú)關(guān)和語(yǔ)言無(wú)關(guān)的重要支柱