一、概念
1.1 無符號數:
以 u1、u2、u3、u4、u8 代表 1 個位元組,2 個位元組、4 個位元組、8 個位元組的無符號數,無符號數可以描述數字,索引參考、數量值和按照 UTF-8 編碼構成的字串值,
1.2 表
- 表是由多個無符號數或其他表作為資料項構成的復合的資料結構,所有表都習慣性的以“_info”結尾,表用于表示有層次關系的復合結構的資料,整個 Class 檔案本質上是一張表
1.3 class 檔案組成
ClassFile {
u4 magic; //魔數, 用于識別class檔案格式
u2 minor_version;//次版本號
u2 major_version;//主版本號
u2 constant_pool_count; //常量池計數器
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags;//訪問標志
u2 this_class;//類索引
u2 super_class;//父類索引
u2 interfaces_count;//介面計數器
u2 interfaces[interfaces_count];//介面索引集合
u2 fields_count;//欄位計數器
field_info fields[fields_count];//欄位表集合
u2 methods_count;//方法計數器
method_info methods[methods_count];//方法表
u2 attributes_count; //屬性計數器
attribute_info attributes[attributes_count];附加屬性表
}
1.4 魔數
每個 Class 檔案的頭 4 個位元組被稱為魔數(Magic Number),它的唯一作用是確定這個檔案是否能被虛擬機接受的 Class 檔案,它的值是 0xCAFEBABE (咖啡寶貝),非常容易記憶,
1.5 版本號
緊接著的位元組是次版本號(minor_version)和主版本號(major_version),Java 的版本號從 45 開始,Java1.1 之后的 JDK 大版本發布主版本號向上加一(Java1.0~Java1.1 使用了 45.0~45.3 的版本號),注意高版本的 JDK 能向下兼容 以前的 Class 檔案,但不能運行以后版本的 Class 檔案,
1.6 常量池
常量池可以理解為 Class 檔案的資源倉庫,
主要存放:
-
字面量(Literal)
-
符號參考(Symbolic References)
- 類和介面的全限定名(Full Qualified Name)
- 欄位的名稱描述符(Descriptor)
- 方法的名稱和描述符
型別 標識 描述 CONSTANT_Class7 類或介面的符號參考 CONSTANT_Fieldref9 欄位的符號參考 CONSTANT_Methodref10 方法的符號參考 CONSTANT_InterfaceMethodref11 介面中方法的符號參考 CONSTANT_String8 字串型別字面量 CONSTANT_Integer3 整型字面量 CONSTANT_Float4 浮點型字面量 CONSTANT_Long5 長整型字面量 CONSTANT_Double6 雙精度浮點型字面量 CONSTANT_NameAndType12 欄位或方法的部分符號參考 CONSTANT_Utf81 UTF-8 編碼字串 CONSTANT_MethodHandle15 標識方法句柄 CONSTANT_MethodType16 標識方法型別 CONSTANT_InvokeDynamic18 動態方法呼叫點
1.7 訪問標識(access_flags)
用于識別類和介面層次的訪問資訊
| Flag Name | Value | Interpretation |
|---|---|---|
ACC_PUBLIC |
0x0001 | 是否為被宣告為 public ,可以被其他外部包中訪問 |
ACC_FINAL |
0x0010 | 是否被宣告 final,不能派生子類 |
ACC_SUPER |
0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction. |
ACC_INTERFACE |
0x0200 | 標識一個介面 |
ACC_ABSTRACT |
0x0400 | 宣告 abstract,抽象類,不能實體化 |
ACC_SYNTHETIC |
0x1000 | 宣告 synthetic; 標識這個類并非有用戶代碼產生 |
ACC_ANNOTATION |
0x2000 | 標識這個一個注解 |
ACC_ENUM |
0x4000 | 標識這是一個列舉 |
1.8 類索引、父類索引和介面索引
Class 檔案就是由這三項資料來確定這個類的繼承關系,類索參考于確定類的全限定類名,父索參考于確定父類的全限定類名,介面索引集合用于描述類實作了那些介面,
1.9 欄位表集合
欄位表集合[field_info] 用于描述介面或者類中宣告的變數,欄位(field) 包括類變數和實體變數,但不包括方法內部宣告的區域變數,
-
欄位表結構
field_info { u2 access_flags; //訪問標識 u2 name_index; //名稱索引 u2 descriptor_index; //描述符索引 u2 attributes_count; //屬性計數器 attribute_info attributes[attributes_count]; //屬性表 } -
欄位包含的資訊:
- 作用域(public 、private、protected 修飾符)
- static 修飾符
- 可變性 final
- 并發可見性 volatile
- 可否序列化 transient
- 欄位型別 【基本資料型別(byte、char、short、int、long 、float、double、boolean)、物件、陣列】
-
欄位訪問標志
ACC_PUBLIC0x0001 Declared public; may be accessed from outside its package.ACC_PRIVATE0x0002 宣告 private;ACC_PROTECTED0x0004 宣告 protected;ACC_STATIC0x0008 宣告 static.ACC_FINAL0x0010 宣告 final;ACC_VOLATILE0x0040 宣告 volatile;ACC_TRANSIENT0x0080 宣告 transient;ACC_SYNTHETIC0x1000 宣告 synthetic; 欄位是否有編譯器自動產生的 ACC_ENUM0x4000 宣告欄位是否是列舉
-
簡單名稱:沒有型別和引數修飾的方法或者欄位名稱,如 inc()和 m 欄位的簡稱為 inc 和 m
-
全限定名:com/demo/TestClass; “;”標識類的全限定名結束
-
描述符:用于描述欄位的資料型別,方法的引數串列(數量、型別、順序)和回傳值
標識字符 代表型別 描述 Bbyte基本型別 byte Cchar基本型別 char Ddouble基本型別 double Ffloat基本型別 float Iint基本型別 int Jlong基本型別 long LClassName;reference物件型別,如 : Ljava/lang/Object Sshort基本型別 short Zbooleanj 基本型別 boolean [reference陣列型別 ,如陣列 int[]被記錄為 [I,陣列String[][]被記錄為 [[java/lang/StringV void 特殊型別 Void
描述符來描述方法時,按照先引數串列,后回傳值的順序描述;如:java.lang.String.toString() 描述為 () Ljava/lang/String,java.lang.String#valueOf(char[], int, int) 描述為 ([CII)Ljava/lang/String
1.10 方法表集合
方法描述采取與欄位描述完全一致的方式,
-
方法表結構
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } -
相關訪問標識
Flag Name Value Interpretation ACC_PUBLIC0x0001 方法是否 public ACC_PRIVATE0x0002 方法是否 private ACC_PROTECTED0x0004 方法是否 protected;ACC_STATIC0x0008 方法是否 static.ACC_FINAL0x0010 方法是否 final;ACC_SYNCHRONIZED0x0020 方法是否 synchronized; 標識同步方法ACC_BRIDGE0x0040 標識是否由編譯器生成的橋接方法 ACC_VARARGS0x0080 方法是否接受不定引數 ACC_NATIVE0x0100 方法是否 native;ACC_ABSTRACT0x0400 方法是否 abstract;ACC_STRICT0x0800 方法是否 strictfp;ACC_SYNTHETIC0x1000 方法是否為 synthetic; -
方法里定義的代碼
方法里面的代碼,經過編譯器編譯成位元組指令后,存放在方法屬性表集合,名為 Code 屬性里,
1.11 屬性表集合在
屬性表(attribute_info)在 Class 檔案、欄位表、方法表中都可以攜帶自己的屬性表集合,用于描述某些場景專有的資訊,
-
格式結構
attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; } -
虛擬機預定義屬性
屬性 位置 含義 class 版本 SourceFileClassFile記錄源檔案名稱 45.3 InnerClassesClassFile內部類串列 45.3 EnclosingMethodClassFile僅當一個類為區域類或匿名類時才能擁有這個屬性,這個屬性用于標識這個類所在的外圍方法 49.0 SourceDebugExtensionClassFileJDK 1.6 中新增的屬性,SourccDcbugExtcnsion 屬性用于在儲額外的除錯資訊,譬如在進行 JSP 檔案除錯時,無法通過 Java 堆疊來定位到 JSP 檔案的行號, JSR-45 規范為這些非 Java 語言撰寫,卻需要編譯成位元組碼并運行在 Java 虛擬機中的程式提供了一個進行除錯的標準機制,使用 SourccDcbugExtcnsion 屬性就可以用于存盤這個標準所新加入的除錯資訊 49.0 BootstrapMethodsClassFileJDK1.7 新增的屬性,用于保存 invokedynamic 指令參考的引導方法限定符 51.0 ConstantValuefield_infofinal 關鍵字定義的常量值 45.3 Codemethod_infoJava 代碼編譯成的位元組碼指令 45.3 Exceptionsmethod_info方法拋出的例外 45.3 RuntimeVisibleParameterAnnotations,RuntimeInvisibleParameterAnnotationsmethod_infoJDK5 中新增的屬性,作用于方法引數 RuntimeVisibleParameterAnnotations 屬性指明哪些注解是運行時可見;RuntimeInvisibleAnnotations`屬性指明哪里注解是運行時不可見的49.0 AnnotationDefaultmethod_infoJDK1.5 中新增的屬性,用于記錄注解類元素的默認值 49.0 MethodParametersmethod_infoMethodParameters 屬性記錄方法的形式引數的資訊,比如方法名稱, 52.0 SyntheticClassFile,field_info,method_info標識方法或欄位為編譯器自動生成的 45.3 DeprecatedClassFile,field_info,method_info被宣告為 Deprecated 的方法和欄位 45.3 SignatureClassFile,field_info,method_info記錄類,介面,建構式,方法或欄位的簽名 49.0 RuntimeVisibleAnnotations,RuntimeInvisibleAnnotationsClassFile,field_info,method_infoJDK5 中新增的屬性,為動態注解提供支持, RuntimeVisibleAnnotations屬性指明哪些注解是運行時可見;RuntimeInvisibleAnnotations屬性指明哪里注解是運行時不可見的49.0 LineNumberTableCodeLineNumberTable 屬性表存放方法的行號資訊 45.3 LocalVariableTableCodeLocalVariableTable 屬性表中存放方法的區域變數資訊 45.3 LocalVariableTypeTableCodeJDK 1.5 中新增的屈件,它使用特征簽名代替描述符,是為了引入泛型語法之后能描述泛型引數化型別而添加 49.0 StackMapTableCodeJDKL6 中新增的屬性.供新的型別檢查驗證器 (Type Checker)檢查和處理目標方法的后部變數和運算元堆疊所需要的型別是否匹配 50.0 RuntimeVisibleTypeAnnotations,RuntimeInvisibleTypeAnnotationsClassFile,field_info,method_info,Codejdk8 新增屬性 RuntimeVisibleTypeAnnotations:運行時可見型別注解RuntimeInvisibleTypeAnnotations:運行時不可見型別注解52.0
-
Code 屬性
Java 程式方法體中的代碼經過 Javac 編譯器處理后,最終成為位元組碼指令存盤在 Code 屬性內,注意并不是所有方法表都存在 Code 屬性,例如,介面和抽象類中的方法就不存在 Code 屬性,
-
Code 屬性格式定義
Code_attribute { u2 attribute_name_index; //指向常量CONSTANT_UTF8_info的索引,常量固定值為Code u4 attribute_length; u2 max_stack; //運算元堆疊 u2 max_locals; //區域變數表所需的存盤空間 //位元組碼長度,最大值可達2^32-1, 但是虛擬機限制了一個方法不允許超過65535條位元組碼指令 //即使用了u2 的長度,超出這個限制會導致編譯失敗 u4 code_length; u1 code[code_length]; //位元組碼指令的子節流 u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }
二、位元組碼指令
2.1 加載和存盤指令
加載和存盤指令用于將資料在堆疊幀中的區域變數表與運算元堆疊之間傳輸,
-
將區域變數加載到運算元堆疊
// i 代表對int 操作 // l 代表對long 操作 // f 代表對float 操作 // d 代表對double 操作 // a 代表對參考reference 操作 // iload_<n> 代表一組指令,iload_0、iload_1、iload_2、iload_3等指令 iload iload_<n> lload lload_<n> fload fload_<n> dload dload_<n> aload aload_<n> -
將數值從運算元堆疊存盤到區域變數表
istore istore_<n> lstore lstore_<n> fstore fstore_<n> dstore dstore_<n> astore astore_<n> -
將常量加載到運算元堆疊
bipush sipush ldc ldc_w ldc2_w aconst_null iconst_ml iconst_<i> lconst_<l> fconst_<f> dconst_<d> -
擴充區域變數表的訪問索引的指令:wide
2.2 運算指令
相關指令
-
加法指令
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、 isbr、 iusbr、 lsbl、 lshr、 lushr -
按位或指令
ior、 lor -
按位與指令
iand、 land -
按位異或指令
ixor、 lxor -
區域變數自增指令
iinc -
比較指令
dcmpg、 dcmpl、 fcmpg、 fcmpl、 lcmp
注意
- 只有當除法指令和求余指令遇到除數為零時,虛擬機會拋出 ArithmeticException 例外
- Java 在處理浮點數運算時,不會拋出任何運行例外(Java 語言的例外)
- 當一個操作產生溢位時,將使用有符號的無窮大表示,如果某個操作結果沒有明確的數學定義的話,將會使用 NaN 表示
- 所有使用 NaN 值作為運算元的算術操作,結果都回傳 NaN
double a = 1;
double b = a / 0; //不會報錯,結果Infinity
double a = 0.0;
double b = a / 0.0; //不會報錯,結果NaN
2.3 型別轉換指令
型別轉換指令可以將兩種不同的數值型別進行互相轉換,一般用于用戶代碼中的顯示型別轉換操作,隱式型別轉換不同轉換指令,虛擬機直接支持,
-
顯示型別轉換指令
i2b int 轉換byte i2c int 轉換char i2s int 轉換short l2i long 轉換 int f2i float 轉換 int f2l float 轉換 long d2i double 轉換 int d2l double 轉換 long d2f double 轉換 float -
轉換規則
- 如果浮點值是 NaN, 那轉換結果就是 int 或者 long 型別的 0
- 如果浮點值不是無窮大的話,浮點值使用 IEEE 754 的向零舍入模式去整,獲取整數值 v,如果 v 在目標型別 T(int 或 long) 的標識表示范圍之內,那轉換結果就是 v,
- 否則,將根據 v 的符號,轉換為 T 所能表示的最大或最小正數,
double nan = 0.0 / 0.0; int a = (int) nan; System.out.println(a); //0 float b = (float) nan; System.out.println(b); //NaN
2.4 物件創建與訪問指令
-
創建類實體指令
new -
創建陣列指令
newarray anewarray multianewarray -
訪問類欄位 和 實體欄位
getfield putfield getstatic putstatic -
加載陣列元素到運算元堆疊
baload //byte陣列 caload //char陣列 saload //short陣列 iaload //int陣列 laload //long 陣列 faload //float 陣列 daload //double 陣列 aaload //物件陣列 -
將運算元堆疊存盤到陣列元素中
bastore castore sastore iastore lastore fastore dastore aastore -
獲取陣列長度
arraylength -
檢查類實體型別的指令
instanceof checkcast
2.5 運算元堆疊的管理指令
-
出堆疊指令
pop pop2 //出堆疊2個元素 -
復制堆疊頂一個或者兩個數值并復制或雙份的復制值重新壓入堆疊頂
dup dup2 dup_x1 dup2_x1 dup_x2 dup2_x2 -
將堆疊最頂端的兩個數值互換
swap
2.6 控制轉移指令
-
條件分支
ifeq iflt ifle ifne ifge ifnull ifnonull if_icmpeq 比較堆疊頂兩個int型別數值的大小 ,當前者 等于 后者時,跳轉 if_icmpne if_icmplt if_icmpgt if_icmple if_icmpge if_acmpeq if_acmpne -
復合條件分支
tableswitch switch 條件跳轉 case值連續 lookupswitch witch 條件跳轉 case值不連續 -
無條件分支
goto 無條件跳轉 goto_w 無條件跳轉 寬索引 jsr SE6之前 finally字句使用 跳轉到指定16位的offset,并將jsr下一條指令地址壓入堆疊頂 jsr_w SE6之前 同上 寬索引 ret SE6之前回傳由指定的區域變數所給出的指令地址(一般配合jsr jsr_w使用) w同區域變數的寬索引含義
2.7 方法呼叫和回傳指令
-
方法呼叫指令
invokevirtual: 呼叫物件實體方法 invokeinterface 呼叫介面方法 invokespecial 呼叫一些需要特需處理的實體方法,包括實體初始化方法、私有方法、父類方法 invokestatic 呼叫類方法 invokedynamic 在運行時動態決議出呼叫點限定符所參考的方法,并執行 -
回傳指令
ireturn lreturn freturn dreturn areturn return 宣告為void 的方法
2.8 例外處理指令
athrow 顯示拋出例外
2.9 同步指令
Java 虛擬機可以支持方法級別的同步和方法內部一段指令序列的同步,這兩種同步結構都使用管理(Monitor)來支持,
-
方法級別的同步是由方法表結構中 ACC_SYNCHRONIZED 訪問標識來處理
-
方法內部一段指令序列的同步
monitorenter 獲取鎖,進入代碼塊 monitorexit 釋放鎖,必須與monitorenter成對出現 -
原始碼
public class SynchronizedInstruction { private Object lock=new Object(); void onlyMe(Object lock){ synchronized (lock){ //doSomething } } } -
反匯編
Compiled from "SynchronizedInstruction.java" public class cn.hdj.jvm.bytecode.SynchronizedInstruction { private java.lang.Object lock; public cn.hdj.jvm.bytecode.SynchronizedInstruction(); void onlyMe(java.lang.Object); } Classfile /home/hdj/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/SynchronizedInstruction.class Last modified 2021-3-20; size 488 bytes MD5 checksum 1f6db0fa955b6d719018d2ea50e1e910 Compiled from "SynchronizedInstruction.java" public class cn.hdj.jvm.bytecode.SynchronizedInstruction SourceFile: "SynchronizedInstruction.java" minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #2.#19 // java/lang/Object."<init>":()V #2 = Class #20 // java/lang/Object #3 = Fieldref #4.#21 // cn/hdj/jvm/bytecode/SynchronizedInstruction.lock:Ljava/lang/Object; #4 = Class #22 // cn/hdj/jvm/bytecode/SynchronizedInstruction #5 = Utf8 lock #6 = Utf8 Ljava/lang/Object; #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 onlyMe #12 = Utf8 (Ljava/lang/Object;)V #13 = Utf8 StackMapTable #14 = Class #22 // cn/hdj/jvm/bytecode/SynchronizedInstruction #15 = Class #20 // java/lang/Object #16 = Class #23 // java/lang/Throwable #17 = Utf8 SourceFile #18 = Utf8 SynchronizedInstruction.java #19 = NameAndType #7:#8 // "<init>":()V #20 = Utf8 java/lang/Object #21 = NameAndType #5:#6 // lock:Ljava/lang/Object; #22 = Utf8 cn/hdj/jvm/bytecode/SynchronizedInstruction #23 = Utf8 java/lang/Throwable { private java.lang.Object lock; flags: ACC_PRIVATE public cn.hdj.jvm.bytecode.SynchronizedInstruction(); flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: new #2 // class java/lang/Object 8: dup 9: invokespecial #1 // Method java/lang/Object."<init>":()V 12: putfield #3 // Field lock:Ljava/lang/Object; 15: return LineNumberTable: line 8: 0 line 9: 4 void onlyMe(java.lang.Object); flags: Code: stack=2, locals=4, args_size=2 0: aload_1 //將lock物件入堆疊 1: dup //復制堆疊頂元素 2: astore_2 //將堆疊頂元素存盤到區域變數表Slot2中 3: monitorenter //以lock物件為鎖,開始同步 4: aload_2 //將區域變數表Slot2中元素入堆疊 5: monitorexit //退出同步 6: goto 14 //程式正常結束,跳轉到14回傳 9: astore_3 //從這步開始是例外路徑,開下面的Exception table 10: aload_2 //將區域變數表Slot2中元素入堆疊 11: monitorexit //退出同步 12: aload_3 //將區域變數表Slot3中元素(例外物件)入堆疊 13: athrow //把例外物件重新拋出個onlyMe方法呼叫者 14: return //方法回傳 Exception table: from to target type 4 6 9 any 9 12 9 any LineNumberTable: line 11: 0 line 13: 4 line 14: 14 StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 9 locals = [ class cn/hdj/jvm/bytecode/SynchronizedInstruction, class java/lang/Object, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4 }
三、例子決議
- 代碼
public class DemoDynamic {
public static void foo() {
int a = 1;
int b = 2;
int c = (a + b) * 5;
}
}
-
javap 命令(也可以使用 IDEA 查看位元組碼工具:jclasslib)
javac -g -encoding utf-8 DemoDynamic.java javap -verbose -c .\DemoDynamic.class > .\DemoDynamic.javap -
位元組檔案
Classfile /D:/IDEA/Java-Learning/src/main/java/cn/hdj/jvm/bytecode/DemoDynamic.class Last modified 2020-10-17; size 419 bytes MD5 checksum 0242e2d86e94eb62d302f5a034336416 Compiled from "DemoDynamic.java" public class cn.hdj.jvm.bytecode.DemoDynamic minor version: 0 //版本號 major version: 52 flags: ACC_PUBLIC, ACC_SUPER //訪問識別符號 Constant pool: //常量池 #1 = Methodref #3.#18 // java/lang/Object."<init>":()V #2 = Class #19 // cn/hdj/jvm/bytecode/DemoDynamic #3 = Class #20 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 LocalVariableTable #9 = Utf8 this #10 = Utf8 Lcn/hdj/jvm/bytecode/DemoDynamic; #11 = Utf8 foo #12 = Utf8 a #13 = Utf8 I #14 = Utf8 b #15 = Utf8 c #16 = Utf8 SourceFile #17 = Utf8 DemoDynamic.java #18 = NameAndType #4:#5 // "<init>":()V #19 = Utf8 cn/hdj/jvm/bytecode/DemoDynamic #20 = Utf8 java/lang/Object { public cn.hdj.jvm.bytecode.DemoDynamic(); //默認的構造方法 descriptor: ()V flags: ACC_PUBLIC Code: //堆疊容量1 , 區域變數表容量1, 引數個數1(因為每個實體方法都會有一個隱藏引數this) stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcn/hdj/jvm/bytecode/DemoDynamic; public static void foo(); //foo() 方法 descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC //識別符號,public static Code: //方法表中Code 屬性 stack=2, locals=3, args_size=0 //堆疊容量2 , 區域變數表容量3, 引數個數0 0: iconst_1 // 將常量值1入堆疊-> 堆疊1=1 1: istore_0 // 將堆疊頂元素存盤到區域變數表Slot1位置 -> 區域0=1 2: iconst_2 // 將常量值2入堆疊 -> 堆疊1=2 3: istore_1 // 將堆疊頂元素存盤到區域變數表Slot2位置 -> 區域1=2 4: iload_0 // 將區域變數表Slot1中元素入堆疊 5: iload_1 // 將區域變數表Slot2中元素入堆疊 6: iadd // 執行相加操作, 1+2 = 3, 入堆疊 7: iconst_5 // 將常量值5入堆疊 8: imul // 執行相乘操作,3*5=15,入堆疊 9: istore_2 // 將堆疊頂元素存盤到區域變數表Slot2位置-> 區域2=15 10: return //回傳 LineNumberTable: //行數表 line 9: 0 line 10: 2 line 11: 4 line 12: 10 LocalVariableTable: //區域變數表 Start Length Slot Name Signature 2 9 0 a I 4 7 1 b I 10 1 2 c I } SourceFile: "DemoDynamic.java"
四、位元組碼增強
具體詳情看 位元組碼增強技術探索,這里只簡單列出相關工具及使用場景,

4.1 ASM
對于需要手動操縱位元組碼的需求,可以使用 ASM,它可以直接生產 .class 位元組碼檔案,也可以在類被加載入 JVM 之前動態修改類行為

- ASM 工具 輔助工具
- IDEA 插件 ASM ByteCode Outline,用于查看類中的代碼對應的 ASM 寫法
4.2 Javassist
利用 Javassist 實作位元組碼增強時,可以無須關注位元組碼刻板的結構,其優點就在于編程簡單,直接使用 java 編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構或者動態生成類,
4.3 Instrument
instrument 是 JVM 提供的一個可以修改已加載類的類別庫,專門為 Java 語言撰寫的插樁服務提供支持,它需要依賴 JVMTI 的 Attach API 機制實作,注意:ASM 和 Javassist 操作位元組碼庫只能在類加載前對類進行強化,
4.5 位元組碼增強技術使用場景
-
AOP 面向切面編程
-
熱部署:不部署服務而對線上服務做修改,可以做打點、增加日志等操作,
-
Mock:測驗時候對某些服務做 Mock,
-
性能診斷工具:比如 bTrace 就是利用 Instrument,實作無侵入地跟蹤一個正在運行的 JVM,監控到類和方法級別的狀態資訊,
參考
- 《深入了解 Java 虛擬機 Java 高級特性和最佳實踐》
- https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
- 虛擬機指令 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5
- https://www.cnblogs.com/noteless/p/9556928.html
- 位元組碼增強技術探索 https://tech.meituan.com/2019/09/05/java-bytecode-enhancement.html
- 輕松看懂 Java 位元組碼 https://juejin.im/post/6844903588716609543
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/270720.html
標籤:其他
上一篇:Kindle使用技巧
