国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

Tonny / 500人閱讀

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

前言

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

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

單刀直入

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

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

cd到這個路徑下,運行javap命令:

javap -v -p UserServiceImpl

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

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

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

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

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

大戲上演

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

Java方法之創世紀

話說Jvm大帝是神之旨意的履行者(Jvm大帝就是虛擬機,神就是開發者,神之旨意是開發者寫好并編譯后的字節碼...),當Jvm大帝帶領Java世界運行進入了一個新的方法后,會為這個方法在棧內存大陸上創造兩個重要的領域:局部變量表和操作數棧。

要有棧。要有表。神說。

依照神之旨意,jvm大帝創造的局部變量表里一般會包含this指針(針對實例方法,靜態方法當然無此)、方法的所有傳入參數和方法中所開辟的本地變量。

那么操作數棧是干嘛用的呢?

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

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

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

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

這里我們看到,變化主要有,指令多了一行,多進行了一次aload,getfield變成了putfield,areturn變成了return,僅此而已。另外領銜主演也就是局部變量表里多了一位,也就是方法的傳入參數serviceType字符串對象了。其情節如下:

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

融會貫通

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

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

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

操作數棧管理指令:如pop和dup,他們只對操作數棧進行操作。

類型轉換指令和運算指令:如add/div/l2i等系列指令,實際上這類指令一般也只對操作數棧進行操作。

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

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

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

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

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

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

這個過程簡單解讀如下:
1.new一個StringBuilder對象(在堆內存中開辟空間),并將其引用入棧,用于實現加號連接字符串功能(相當于C++中的運算符重載);
2.dup復制棧頂的剛剛放入的引用,再次壓棧,這時棧里有兩個重復的內容,深度為2;
3.調用并彈出棧頂StringBuilder引用對象的方法,棧深度為1;
4.(綠色部分)調用UUID.randomUUID()靜態方法,結果壓棧后彈出調用String的toString方法,再壓棧,棧深度為2;
5.(黃色部分)將"-"和""字符壓棧,此時棧深度為4,彈出(棧頂3個元素)調用replace方法,結果壓棧,深度為2;
6.調用StringBuilder對象的append方法,結果壓棧,深度為1;
7.(藍色部分)將參數user壓棧并調用hashCode方法,結果壓棧,深度為2;
8.調用StringBuilder對象的append方法(此處和上面的append調用共同完成了加號功能,在圖中為紅色部分),結果壓棧,深度為1,再調用toString方法后結果壓棧,深度為1;
9.areturn返回棧頂對象。

再看這個包含if跳轉的方法login:

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

后話

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

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

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

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

關注最新技術分享和資訊:TechHome,技術人之家!

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66764.html

相關文章

  • Java字節修改神器HiBeaver:黑掉你的SDK

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

    Lavender 評論0 收藏0
  • 一次Java字節插樁實戰

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

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

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

    seal_de 評論0 收藏0

發表評論

0條評論

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