成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

大話+圖說(shuō):Java字節(jié)碼指令——只為讓你懂

Tonny / 507人閱讀

摘要:有點(diǎn)基礎(chǔ)的人一定都知道,命令會(huì)將源文件編譯成字節(jié)碼文件,即文件,其中就包含了大量的字節(jié)碼指令。關(guān)于字節(jié)碼指令的分類,可以從兩個(gè)維度進(jìn)行一是指令的功能,二是指令操作的數(shù)據(jù)類型。

前言

隨著Java開(kāi)發(fā)技術(shù)不斷被推到新的高度,對(duì)于Java程序員來(lái)講越來(lái)越需要具備對(duì)更深入的基礎(chǔ)性技術(shù)的理解,比如Java字節(jié)碼指令。不然,可能很難深入理解一些時(shí)下的新框架、新技術(shù),盲目一味追新也會(huì)越來(lái)越感乏力。

本文既不求照本宣科,亦不求炫技或著文立說(shuō),僅力圖以最簡(jiǎn)明、最形象生動(dòng)的方式,結(jié)合例子與實(shí)戰(zhàn),讓小白也能搞懂這門(mén)看似復(fù)雜的技術(shù)概念。

單刀直入

閑言碎語(yǔ)不要講,先表一表,什么是Java字節(jié)碼指令?簡(jiǎn)而言之,Java字節(jié)碼指令就是Java虛擬機(jī)能夠聽(tīng)得懂、可執(zhí)行的指令,可以說(shuō)是Jvm層面的匯編語(yǔ)言,或者說(shuō)是Java代碼的最小執(zhí)行單元。
有點(diǎn)Java基礎(chǔ)的人一定都知道,javac命令會(huì)將Java源文件編譯成字節(jié)碼文件,即.class文件,其中就包含了大量的字節(jié)碼指令。因此可以將javac命令理解為一個(gè)翻譯命令,將源文件翻譯成Jvm可以執(zhí)行的指令。
那么最直觀的探究方法莫過(guò)于直接對(duì)比翻譯前后的內(nèi)容。
具體如何對(duì)比呢?就不得不用到Java為我們一直默默提供的一項(xiàng)利器,javap命令,它可以解析字節(jié)碼,將字節(jié)碼內(nèi)部邏輯以可讀的方式呈現(xiàn)出來(lái)。為了緊貼實(shí)戰(zhàn),我們直接在新建的Java工程里,寫(xiě)這樣一個(gè)UserServiceImpl類,里面包含幾個(gè)由簡(jiǎn)單到復(fù)雜的方法,以及一個(gè)名為serviceType的屬性:

如圖,以上方法,復(fù)雜度由低到高依次為:getServiceType下面我們編譯工程,然后在下圖所示的目錄(gradle編譯工程)找到該類的字節(jié)碼文件:

cd到這個(gè)路徑下,運(yùn)行javap命令:

javap -v -p UserServiceImpl

就可以觀看到翻譯版的Java字節(jié)碼的胴體了!這里的-v意思是啰嗦模式,會(huì)輸出全面的字節(jié)碼信息,而-p是指涵蓋所有成員。原字節(jié)碼信息輸出內(nèi)容較多,基于本文的目標(biāo),取其一方法的內(nèi)容,整理如下圖:
方法1,getServiceType():

這個(gè)getServiceType的方法應(yīng)該是再簡(jiǎn)單不過(guò)的Java代碼,翻譯成字節(jié)碼后也變成了三行,我們先來(lái)簡(jiǎn)單推理一下:第一句,aload_0不知所云,索性略過(guò);第二行,getfield應(yīng)該可以讀懂,后面這個(gè)#8似乎是他的參數(shù)(實(shí)際上是對(duì)常量池的引用),//后面注釋的內(nèi)容是javap給我們加上的,意思應(yīng)該是#2的指向是"Field serviceType:Ljava/lang/String;"這個(gè)內(nèi)容。
所以getfield這一行就是取出serviceType這個(gè)字段嘍,so easy。areturn肯定就是return的意思,a的含義也先略過(guò)不表??傊褪侨〕鰏erviceType字段然后return嘍。

那么現(xiàn)在的問(wèn)題就是aload_0是什么意思了,看似多余,但仔細(xì)思考一下,似乎之前給getfield指令傳入了“Field serviceType:Ljava/lang/String;”這樣一個(gè)并不完整的參數(shù),其后半部分的“Ljava/lang/String;”僅僅表示這個(gè)serviceType字段的類型是String,也就是說(shuō),整個(gè)參數(shù)里沒(méi)有說(shuō)是取的誰(shuí)的serviceType字段??!究竟是get誰(shuí)的feild呢?

由此可以想到:aload操作一定是在為getfield指令準(zhǔn)備了一個(gè)主體。

實(shí)際上,再結(jié)合下面的局部變量表,aload_0中的0正是局部變量表里的Slot 0的含義。意思是將局部變量表里的Slot 0的東西壓入操作數(shù)棧,這個(gè)Slot 0里的東西name正是this,也就是UserServiceImpl的實(shí)例,即getfield的主體。

大戲上演

好了,對(duì)于小白同學(xué)有些陌生的概念來(lái)了,啥是操作數(shù)棧?啥是局部變量表?
其實(shí)這兩個(gè)東西理解好了,關(guān)于虛擬機(jī)指令就懂了一大半了。
那么,不妨刪繁就簡(jiǎn),由易入難,先講一個(gè)這樣的故事,故事起名叫:

Java方法之創(chuàng)世紀(jì)

話說(shuō)Jvm大帝是神之旨意的履行者(Jvm大帝就是虛擬機(jī),神就是開(kāi)發(fā)者,神之旨意是開(kāi)發(fā)者寫(xiě)好并編譯后的字節(jié)碼...),當(dāng)Jvm大帝帶領(lǐng)Java世界運(yùn)行進(jìn)入了一個(gè)新的方法后,會(huì)為這個(gè)方法在棧內(nèi)存大陸上創(chuàng)造兩個(gè)重要的領(lǐng)域:局部變量表和操作數(shù)棧。

要有棧。要有表。神說(shuō)。

依照神之旨意,jvm大帝創(chuàng)造的局部變量表里一般會(huì)包含this指針(針對(duì)實(shí)例方法,靜態(tài)方法當(dāng)然無(wú)此)、方法的所有傳入?yún)?shù)和方法中所開(kāi)辟的本地變量。

那么操作數(shù)棧是干嘛用的呢?

我們?cè)僖肓硗庖粋€(gè)比喻,如果把運(yùn)行Java方法理解為拍戲,那么局部變量表里的各個(gè)局部變量就是這部戲的核心主角,或者說(shuō)領(lǐng)銜主演,而操作數(shù)棧正是這部戲的舞臺(tái)。所謂操作數(shù)棧搭臺(tái),局部變量唱戲,是也。那么aload_0就是告訴Jvm導(dǎo)演(大帝已淪落為導(dǎo)演),請(qǐng)0號(hào)演員this同志登臺(tái)(壓棧),演后邊的本子。
當(dāng)然了,這個(gè)比喻并不完全恰當(dāng),因?yàn)椴僮鲾?shù)棧并不是“舞臺(tái)”的結(jié)構(gòu),而是棧的結(jié)構(gòu)。但是這個(gè)比喻可以很好地說(shuō)明局部變量表和操作數(shù)棧之間的關(guān)系,以及aload_0的作用。

下面我們用一張圖來(lái)演示一下getServiceType這個(gè)小劇本橋段所導(dǎo)演的故事:

好吧這部劇雖然短的可憐,但已經(jīng)基本把指令、操作數(shù)棧和局部變量表三者的關(guān)系演繹了出來(lái)。
值得注意的是,getfield這條指令對(duì)操作數(shù)棧進(jìn)行了復(fù)合操作,其流程可以示意如下圖:

后面我們將要接觸到的許多指令都如此,指令內(nèi)部執(zhí)行了彈出—>處理—>壓回的流程。
下面我們就來(lái)分析一個(gè)相對(duì)復(fù)雜一點(diǎn)的方法,setServiceType(String),如下圖:

這里我們看到,變化主要有,指令多了一行,多進(jìn)行了一次aload,getfield變成了putfield,areturn變成了return,僅此而已。另外領(lǐng)銜主演也就是局部變量表里多了一位,也就是方法的傳入?yún)?shù)serviceType字符串對(duì)象了。其情節(jié)如下:

這里,putfield只彈出棧內(nèi)的操作數(shù),而沒(méi)有向操作數(shù)棧壓回任何數(shù)據(jù),而且執(zhí)行putfield之前,棧內(nèi)元素的位置也必須符合“值在上,主體在下”要求。
而最后的return僅表示方法結(jié)束,而不會(huì)像areturn一樣返回棧頂元素。這也印證了setServiceType(String)方法沒(méi)有返回參數(shù)。

融會(huì)貫通

相信有了以上的講解,大家對(duì)指令、操作數(shù)棧、局部變量表三者的運(yùn)作關(guān)系有了一定認(rèn)識(shí),為了后邊能夠分析更復(fù)雜的方法,這里必須概括性地講解一下更多的Java字節(jié)碼指令。雖然Java字節(jié)碼指令非常多,但其實(shí)常用的不外乎幾個(gè)類別,先從這幾個(gè)常用類別入手理解,便可漸入佳境。
關(guān)于字節(jié)碼指令的分類,可以從兩個(gè)維度進(jìn)行:一是指令的功能,二是指令操作的數(shù)據(jù)類型。我們先從功能說(shuō)起,指令主要可以分為如下幾類:

存儲(chǔ)和加載類指令:主要包括load系列指令、store系列指令和ldc、push系列指令,主要用于在局部變量表、操作數(shù)棧和常量池三者之間進(jìn)行數(shù)據(jù)調(diào)度;(關(guān)于常量池前面沒(méi)有特別講解,這個(gè)也很簡(jiǎn)單,顧名思義,就是這個(gè)池子里放著各種常量,好比片場(chǎng)的道具庫(kù))

對(duì)象操作指令(創(chuàng)建與讀寫(xiě)訪問(wèn)):比如我們剛剛的putfield和getfield就屬于讀寫(xiě)訪問(wèn)的指令,此外還有putstatic/getstatic,還有new系列指令,以及instanceof等指令。

操作數(shù)棧管理指令:如pop和dup,他們只對(duì)操作數(shù)棧進(jìn)行操作。

類型轉(zhuǎn)換指令和運(yùn)算指令:如add/div/l2i等系列指令,實(shí)際上這類指令一般也只對(duì)操作數(shù)棧進(jìn)行操作。

控制跳轉(zhuǎn)指令:這類里包含常用的if系列指令以及goto類指令。

方法調(diào)用和返回指令:主要包括invoke系列指令和return系列指令。這類指令也意味這一個(gè)方法空間的開(kāi)辟和結(jié)束,即invoke會(huì)喚醒一個(gè)新的java方法小宇宙(新的棧和局部變量表),而return則意味著這個(gè)宇宙的結(jié)束回收。

如下圖,展示了各類指令的作用:

再?gòu)牧硗庖粋€(gè)維度,即指令操作的數(shù)據(jù)類型來(lái)講:指令開(kāi)頭或尾部的一些字母,就往往表明了它所能操作的數(shù)據(jù)類型:

a對(duì)應(yīng)對(duì)象,表示指令操作對(duì)象性數(shù)據(jù),比如aload和astore、areturn等等。
i對(duì)應(yīng)整形。也就有iload,istore等i系列指令。
f對(duì)應(yīng)浮點(diǎn)型。
l對(duì)應(yīng)long,b對(duì)應(yīng)byte,d對(duì)應(yīng)double,c對(duì)應(yīng)char。
另外地,ia對(duì)應(yīng)int array,aa對(duì)應(yīng)object array,da對(duì)應(yīng)double array。不在一一贅述。

了解了以上內(nèi)容,我們?cè)偃タ醋詈髱讉€(gè)方法,應(yīng)該就會(huì)容易理解很多了。
下面我們就直搗黃龍genToken這個(gè)方法(圖中的顏色暗示了指令和方法調(diào)用之間的關(guān)系):

這個(gè)過(guò)程簡(jiǎn)單解讀如下:
1.new一個(gè)StringBuilder對(duì)象(在堆內(nèi)存中開(kāi)辟空間),并將其引用入棧,用于實(shí)現(xiàn)加號(hào)連接字符串功能(相當(dāng)于C++中的運(yùn)算符重載);
2.dup復(fù)制棧頂?shù)膭倓偡湃氲囊?,再次壓棧,這時(shí)棧里有兩個(gè)重復(fù)的內(nèi)容,深度為2;
3.調(diào)用并彈出棧頂StringBuilder引用對(duì)象的方法,棧深度為1;
4.(綠色部分)調(diào)用UUID.randomUUID()靜態(tài)方法,結(jié)果壓棧后彈出調(diào)用String的toString方法,再壓棧,棧深度為2;
5.(黃色部分)將"-"和""字符壓棧,此時(shí)棧深度為4,彈出(棧頂3個(gè)元素)調(diào)用replace方法,結(jié)果壓棧,深度為2;
6.調(diào)用StringBuilder對(duì)象的append方法,結(jié)果壓棧,深度為1;
7.(藍(lán)色部分)將參數(shù)user壓棧并調(diào)用hashCode方法,結(jié)果壓棧,深度為2;
8.調(diào)用StringBuilder對(duì)象的append方法(此處和上面的append調(diào)用共同完成了加號(hào)功能,在圖中為紅色部分),結(jié)果壓棧,深度為1,再調(diào)用toString方法后結(jié)果壓棧,深度為1;
9.areturn返回棧頂對(duì)象。

再看這個(gè)包含if跳轉(zhuǎn)的方法login:

如上圖,圖中已經(jīng)說(shuō)明的比較全面了,不再贅述。值得一提的是,Java的這種基于棧結(jié)構(gòu)的指令,在設(shè)計(jì)上有一種非常簡(jiǎn)潔的美感,指令與指令之間并沒(méi)有較重的依賴,每條指令僅僅與操作數(shù)棧等領(lǐng)域內(nèi)的數(shù)據(jù)發(fā)生關(guān)系,充滿著某種平衡與秩序感。因此也必須注意,幾乎每條指令的運(yùn)行都有其前提,比如在invokevirtual或invokespecial指令執(zhí)行前,必須保證操作數(shù)棧內(nèi)提前按順序壓入好所需的操作數(shù),否則就會(huì)發(fā)生問(wèn)題。
關(guān)于最復(fù)雜的onCreate方法,就不再啰嗦解讀了,讀者可以前往我的github上的對(duì)應(yīng)demo repo,進(jìn)入tutorial分支,拉取源碼和教程資源,或者自己寫(xiě)demo體驗(yàn)這一完整過(guò)程。
地址:https://github.com/BryanSharp...

后話

關(guān)于實(shí)戰(zhàn),一是可以學(xué)習(xí)使用強(qiáng)大開(kāi)源工具ASM.jar;二是,可以參考本人的另一篇文章:Java字節(jié)碼修改神器HiBeaver:黑掉你的SDK以及一次Android字節(jié)碼插樁實(shí)戰(zhàn),利用hibeaver這個(gè)助手,開(kāi)發(fā)者可以非常靈活地對(duì)字節(jié)碼進(jìn)行修改,插入指令,hook代碼,甚至建立一些簡(jiǎn)單的AOP框架,對(duì)于Java字節(jié)碼學(xué)習(xí)大有裨益。
hibeaver完全開(kāi)源,github項(xiàng)目地址:https://github.com/BryanSharp...

祝玩的愉快!
本文如有不妥之處,歡迎交流指正。

另外,本文為了盡可能地簡(jiǎn)明生動(dòng)、直入核心,簡(jiǎn)化了很多概念和細(xì)節(jié),讀者須知實(shí)際情況的更為復(fù)雜。但相信在理解了本文以后,就可以抓住Java字節(jié)碼指令的核心理念,也就算扣開(kāi)虛擬機(jī)學(xué)習(xí)的大門(mén)并可以開(kāi)始讀書(shū)精進(jìn)了。下面盜圖一張(后有出處),可作拓展:

鏈接:http://blog.csdn.net/luanloui...

關(guān)注最新技術(shù)分享和資訊:TechHome,技術(shù)人之家!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66764.html

相關(guān)文章

  • Java字節(jié)修改神器HiBeaver:黑掉你的SDK

    摘要:下面我們正式開(kāi)始嘗試小米推送,首先,找出其業(yè)務(wù)邏輯中的一個(gè)節(jié)點(diǎn)。因?yàn)樾∶淄扑褪巧虡I(yè)產(chǎn)品,這里不便于探索太多內(nèi)容,但是通過(guò)這個(gè)插件可以比較方便的進(jìn)行類似的研究。 前言 有時(shí)候我們?cè)贘ava開(kāi)發(fā)過(guò)程中可能有這樣的需求:需要研究或者修改工程依賴的Jar包中的一些邏輯,查看代碼運(yùn)行中Jar包代碼內(nèi)部的取值情況(比如了解SDK與其服務(wù)器通信的請(qǐng)求報(bào)文加密前的情況)。 這個(gè)需求類似于Hook。 但...

    Lavender 評(píng)論0 收藏0
  • 一次Java字節(jié)插樁實(shí)戰(zhàn)

    摘要:理解本文需要一定的字節(jié)碼指令基礎(chǔ),可以閱讀筆者的另一篇文章大話圖說(shuō)字節(jié)碼指令只為讓你懂利用字節(jié)碼插樁技術(shù)可以很方便地幫助我們實(shí)現(xiàn)很多手術(shù)刀式的代碼設(shè)計(jì),如無(wú)埋點(diǎn)統(tǒng)計(jì)上報(bào)輕量級(jí)等。 理解本文需要一定的Java字節(jié)碼指令基礎(chǔ),可以閱讀筆者的另一篇文章:大話+圖說(shuō):Java字節(jié)碼指令——只為讓你懂 利用Android字節(jié)碼插樁技術(shù)可以很方便地幫助我們實(shí)現(xiàn)很多手術(shù)刀式的代碼設(shè)計(jì),如無(wú)埋點(diǎn)統(tǒng)計(jì)...

    eternalshallow 評(píng)論0 收藏0
  • 圖說(shuō) WebAssembly(五):高性能原因

    摘要:本文是圖說(shuō)系列文章的第五篇。這樣的話,使用的開(kāi)發(fā)者也不需要做任何適配,但是它們卻能獲得更高性能。該圖并不是用來(lái)準(zhǔn)確的衡量其性能的。運(yùn)行編寫(xiě)出高性能的代碼是可能的。這種清理工作由引擎自動(dòng)進(jìn)行,稱為垃圾回收。 本文是圖說(shuō) WebAssembly 系列文章的第五篇。如果您還未閱讀之前的文章,建議您從第一篇入手。 在上一篇文章中,我們說(shuō)到了使用 WebAssembly 和 JavaScript...

    seal_de 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<