亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

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

Tonny / 565人閱讀

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

前言

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

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

單刀直入

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

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

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

javap -v -p UserServiceImpl

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

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

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

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

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

大戲上演

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

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

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

要有棧。要有表。神說。

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

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

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

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

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

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

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

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

融會貫通

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

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

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

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

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

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

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

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

再從另外一個維度,即指令操作的數(shù)據(jù)類型來講:指令開頭或尾部的一些字母,就往往表明了它所能操作的數(shù)據(jù)類型:

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

了解了以上內(nèi)容,我們再去看最后幾個方法,應(yīng)該就會容易理解很多了。
下面我們就直搗黃龍genToken這個方法(圖中的顏色暗示了指令和方法調(diào)用之間的關(guān)系):

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

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

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

后話

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

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

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

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

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

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

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/66764.html

相關(guān)文章

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

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

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

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

    eternalshallow 評論0 收藏0
  • 圖說 WebAssembly(五):高性能原因

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

    seal_de 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<