在使用字節碼文件分析 i = i++ 以前,咱們先來看一些必要的前置知識,若是你已經懂了,能夠直接略過小程序
之前咱們常常說,Java 是跨平臺的語言,可是 Java 爲何跨平臺呢?實際上是 JVM 的功勞,JVM實際上是一種規範,HotSpot、J九、Taobao VM、Zing 等等都是它的具體實現。同時,咱們也能夠將 Java 虛擬機理解爲執行在 OS 的一個軟件,理論上也是醬紫的。Java 文件被編譯成爲 class 文件,而後這個 class 文件由 JVM 來進行解析執行this
明白了這一點以後再來分析,既然 JVM 纔是跨平臺的關鍵,而 JVM 是用來解析執行 class 文件的,那麼就能夠推測出全部能編譯爲 class 文件的語言都是能夠在 JVM 上解析執行,作到跨平臺spa
實際上也是如此的,目前已經有巨多的語言作到了,不再侷限於 Java!好比 scala、kotlin、groovy、clojure、jython(我沒有寫錯)等等插件
從跨平臺的語言到跨語言的平臺,你瞭解了麼線程
沒有比下面這個更簡單的 Java 小程序了scala
package com.bl.classloader;
/**
* @Author BarryLee
*/
public class ByteCode1 {
}
code
咱們嘗試着使用 sublime 打開它生成的字節碼文件,下面是前面的三行的十六進制對象
cafe babe 0000 0034 0010 0a00 0300 0d07
000e 0700 0f01 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69blog
個人天,這是人看的嘛。。莫急,來看看介個(來自馬老師的圖)接口
而後再一個個看,能和上邊對應起來的,有興趣能夠看看 JVMS 這個官方文檔
magic 咱們通常把他叫作魔數,其實這四字節它是標識着這個文件是什麼文件,比方說這裏就是 CAFE BABE,就代表了人家是一個 class 文件,想一想看,正好跟 Java 的 logo 相呼應,很好記minor 小版本號
major version 大版本號,這裏十六進制的 34 也就是對應着十進制的 52,也就是 Java8
constant pool_count 常量池裏有多少個常量
constant pool 常量池裏都有些啥,劃重點,等等要烤的
access flags 這個類的標識信息,好比 private、default、protected、public,是 class 仍是 interface
this_class 當前類
super_class 還要講嘛,父類
interface_count 接口的數量
interfaces 具體的接口
filds_count 有多少個字段
fields 有哪些字段,也就是屬性啦
methods_count 方法有多少個
methods 方法都有些啥
attribute_count 參數個數
attribute 參數,裏面最最重要的就是 code -- 代碼
十六進制數確實不是人看的,因此,在你的 IDEA 裝個插件吧
直接在插件商店搜 jclasslib 就能找到它了,下它,使用很簡單,代碼裏點一下你想看的類,而後 view -> shwo bytecode with jclasslib 就能夠啦
打開前面寫的 ByteCode1
是否是很棒,插件已經幫咱們分析好了
verification 校驗字節碼文件是否合法,magic 是否爲 cafe babe
preparation 靜態變量賦默認值(注意不是你在代碼裏給定的初始值)
resolution 將符號引用解析爲直接引用
最後一步了,很重要哦。
Program Counter 程序計數器,簡稱 PC,用來放指令的位置,也就是說能夠用它來找到下一條待執行的指令JVM Stacks 也就是 Java 虛擬機棧,內容有點多仍是放到下面講
Native Method Stacks 本機方法棧,也就是 C++ 這種底層的東西了,咱們管不着
Heap 堆,對象的存放位置,因此也就是GC 的重點
Method Area 方法區,方法區其實只是一個概念,在 1.8 及以後的實現叫作 meta space
Direct Memory 直接內存
聊聊 JVM Stack
每一個線程都會有一個 JVM Stack,每一個 JVM Stack 都會有不少的棧幀 - Stack Frame,每一個方法都對應着一個個的 Stack Frame 壓在 JVM Stack 裏頭
每一個 Frame 又有四個內容,是等等分析 i = i++ 的重點要掌握的,嗯,看個人 xmind 吧,懶得寫了
來看這段小程序,若是時 j = i++,也就是先賦值後加加,相信你必定知道 j 確定是 8,但如今是 i = i++,輸出結果倒是 8,下面咱們經過字節碼的層面來分析這個小程序(打開你的 jclasslib)
public static void main(String[] args) {
int i = 8;
i = i++;
// i = ++i;
System.out.println(i);
}
下面這是所謂的 JVM 指令,學過彙編的應該能看懂一些簡單的指令,不單獨講了,想了解更多,直接點它,jclasslib 幫你直接跳轉到官網,看看官網,就好了
下面這是前邊講的局部變量表
咱們來一個個指令的分析
bipush 8 將 8 壓棧(操做數棧)istore_1,首先將 8 彈棧,而後放到局部變量表,等同於賦值,將 8 給了 i
iload_1, 再把 i 的值 8 又壓到棧裏
iinc 1 by 1 局部變量表 No.爲 1 對應的數 +1,變成了 9
istore_1 而後又把棧裏的 8 彈出賦值給了局部表量表的 1 位置,也就是覆蓋了
所以,i=i++ 最後爲8,下面的指令就是打印以及 return 了
不知道你看出來區別了沒有,i = i++ 是先把 8 壓棧,後面又彈出來覆蓋了局部變量表
而這裏是先iinc 1 by 1了,而後把 9 壓棧,在從新賦值給 i,額,實際上是多餘的一次賦值,寫成 ++i 就得了
仍是簡單解讀如下這些指令8^_^
bipush -> b + i + push -> i很小byte能裝下 + 變量i + 壓棧
istore -> i + store -> 彈棧而後賦值給局部變量表的i
iinc 1 by 1 -> i + inc + 1 + by 1-> 局部變量表爲1的位置加一
getstatic 我也沒看過
invokevirtual 這個比較複雜,是 invoke指令的一種,學過反射的同窗都應該知道 invoke 就是執行方法
- InvokeStatic 執行靜態方法
- InvokeVirtual 是自帶多態的一個指令,好比 List l = new ArrayList(),調用l.add()就是這個virtual了
- InvokeInterface 接口,
- InovkeSpecial 能夠直接定位,不須要多態的方法 private 方法,構造方法
- InvokeDynamic JVM最難的指令 lambda表達式或者反射或者其餘動態語言scala kotlin,或者CGLib ASM,動態產生的class,會用到的指令
嗯,沒了,相信到這裏你已經完全懂了 i = i++ 這個無賴的梗了