本文主要內容:
ASM是什么 JVM指令
Java字節碼文件
ASM編程模型
ASM示例
參考資料匯總
JVM詳細指令
ASM是一個Java字節碼操縱框架,它能被用來動態生成類或者增強既有類的功能。ASM可以直接產生二進制class文件,也可以在類被加載入Java虛擬機之前動態改變類行為。Java class被存儲在嚴格格式定義的.class文件里,這些類文件擁有足夠的元數據來解析類中的所有元素:類名稱、方法、屬性以及 Java 字節碼(指令)。ASM從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據用戶要求生成新類。
目前許多框架如cglib、Hibernate、Spring都直接或間接地使用ASM操作字節碼,有些語言如Jython、JRuby、Groovy也是如此。而類ASM字節碼工具還有:
- BCEL:Byte Code Engineering Library (BCEL),這是Apache Software Foundation 的Jakarta 項目的一部分。BCEL是 Java classworking 最廣泛使用的一種框架,它可以讓您深入 JVM 匯編語言進行類操作的細節。BCEL與Javassist 有不同的處理字節碼方法,BCEL在實際的JVM 指令層次上進行操作(BCEL擁有豐富的JVM 指令級支持)而Javassist 所強調的源代碼級別的工作。
- JBET:通過JBET(Java Binary Enhancement Tool )的API可對Class文件進行分解,重新組合,或被編輯。JBET也可以創建新的Class文件。JBET用一種結構化的方式來展現Javabinary (.class)文件的內容,並且可以很容易的進行修改。
- Javassist:Javassist是一個開源的分析、編輯和創建Java字節碼的類庫。是由東京技術學院的數學和計算機科學系的 Shigeru Chiba 所創建的。它已加入了開放源代碼JBoss 應用服務器項目,通過使用Javassist對字節碼操作為JBoss實現動態AOP框架。
- cglib:是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現Java接口,cglib封裝了asm,可以在運行期動態生成新的 class,Hibernate和Spring都用到過它。cglib用於AOP,jdk中的proxy必須基於接口,cglib卻沒有這個限制。
而ASM與cglib、serp和BCEL相比,ASM有以下的優點 :
- ASM 具有簡單、設計良好的 API,這些 API 易於使用;
- ASM 有非常良好的開發文檔,以及可以幫助簡化開發的 Eclipse 插件;
- ASM 支持 Java 6(ASM3)、Java7(ASM4)、Java(ASM5);
- ASM 很小、很快、很健壯;
- ASM 有很大的用戶群,可以幫助新手解決開發過程中遇到的問題;
- ASM 的開源許可可以讓你幾乎以任何方式使用它;
如果使用ASM框架,需要對JVM指令和Java字節碼文件的結構都需要有點概念。JVM指令總結如下(詳細看參考本文底部的PS)
- 凡是帶const的表示將什么數據壓操作數棧;如:
iconst_2 將int型數據2壓入到操作數棧;
aconst_null 將null值壓入棧; - bipush和sipush 表示將單字節或者短整形的常量值壓入操作數棧;
- 帶ldc的表示將什么類型數據從常量池中壓入到操作數棧;如:
ldc_w 將int或者flat或者string類型的數據壓入到操作數棧;
ldc2_w 將long或者double類型的數據壓入到操作數棧; - 凡是帶load的指令表示將某類型的局部變量數據壓入到操作數棧的棧頂;如:
iload 表示將int類型的局部變量壓入到操作數棧的棧頂;
aload 以a開頭的表示將引用類型的局部變量壓入到操作數棧的棧頂;
iload_1 將局部變量數組里面下標為1的int類型的數據壓入到操作數棧;
iaload 將int型數組的指定索引的值壓入到操作數棧; - 凡是帶有store指令的表示將操作數棧頂的某類型的值存入指定的局部變量中;如:
istore 表示將棧頂int類型的數據存入到指定的局部變量中;
istore_3 表示將棧int類型的數據存入到局部變量數組的下標為3的元素中; - pop 將棧頂數據彈出;pop2將棧頂的一個long或者double數據從棧頂彈出來;
- dup 復制棧頂的數據並將復制的值也壓入到棧頂;
dup2 復制棧頂一個long或者是double的數據並將復制的值也壓入到棧頂; - swap 將棧最頂端的兩個值互換;
- iadd 將棧頂兩個int型的數據相加然后將結果再次的壓入到棧頂;
isub 將棧頂兩個int型的數據相減然后將結果再次的壓入到棧頂;
imul 將棧頂兩個int型的數據相乘然后將結果再次的壓入到棧頂;
idiv 將棧頂兩個int型的數據相除然后將結果再次的壓入到棧頂;
irem 將棧頂兩個int型的數據取模運算然后將結果再次的壓入到棧頂;
ineg 將棧頂的int數據取負將結果壓入到棧頂;
iinc 將指定的int變量增加指定值(i++,i--,i+=2);
i2l 將棧頂int類型數據強制轉換成long型將結果壓入到棧頂;
lcmp 將棧頂兩long型數據的大小進行比較,並將結果(1,0,-1)壓入棧頂; - 以if開頭的指令都是跳轉指令;
- tableswitch、lookupswitch 表示用switch條件跳轉;
- ireturn 從當前方法返回int型數據;
- getstatic 獲取指定類的靜態域,將將結果壓入到棧頂;
putstatic 為指定的類的靜態域賦值;
getfield 獲取指定類的實例變量,將結果壓入到棧頂;
putfield 為指定類的實例變量賦值;
invokevirtual 調用實例方法;
invokespacial 調用超類構造方法,實例初始化方法,私有方法;
invokestatic 調用靜態方法;
invokeinterface 調用接口方法;
new 創建一個對象,並將其引用壓入到棧頂;
newarray 創建一個原始類型的數組,並將其引用壓入到棧頂;
arraylength 獲得一個數組的長度,將將結果壓入到棧頂;
athrow 將棧頂的異常拋出;
checkcast 檢驗類型轉換,轉換未通過,將拋出ClassCastException.
instanceof 檢驗對象是否是指定的類的實例,如果是將1壓入棧頂,否則將0壓入棧頂
monitorenter 獲得對象的鎖,用於同步方法或同步塊
monitorexit 釋放對象的鎖,用於同步方法或同步塊
ifnull 為null時跳轉
ifnonnull 不為null時跳轉
所謂 Java 字節碼文件,就是通常用 javac 編譯器產生的 .class 文件。這些文件具有嚴格定義的格式。為了更好的理解 ASM,首先對 Java 字節碼文件格式作一點簡單的介紹。Java 源文件經過 javac 編譯器編譯之后,將會生成對應的二進制文件(如下圖所示)。每個合法的 Java 字節碼文件都具備精確的定義,而正是這種精確的定義,才使得 Java 虛擬機得以正確讀取和解釋所有的 Java 字節碼文件。
Java 字節碼文件是 8 位字節的二進制流。數據項按順序存儲在 class 文件中,相鄰的項之間沒有間隔,這使得 class 文件變得緊湊,減少存儲空間。在 Java 字節碼文件中包含了許多大小不同的項,由於每一項的結構都有嚴格規定,這使得 class 文件能夠從頭到尾被順利地解析。下面讓我們來看一下 Java 字節碼文件的內部結構,以便對此有個大致的認識。
例如,一個最簡單的 Hello World 程序:
- public class HelloWorld {
- public static void main(String[] args) {
- System.out.println("Hello world");
- }
- }
從上圖中可以看到,一個 Java 字節碼文件大致可以歸為 10 個項:
- Magic:該項存放了一個 Java 字節碼文件的魔數(magic number)和版本信息。一個 Java 字節碼文件的前 4 個字節被稱為它的魔數。每個正確的 Java 字節碼文件都是以 0xCAFEBABE 開頭的,這樣保證了 Java 虛擬機能很輕松的分辨出 Java 文件和非 Java 文件。
- Version:該項存放了 Java 字節碼文件的版本信息,它對於一個 Java 文件具有重要的意義。因為 Java 技術一直在發展,所以字節碼文件的格式也處在不斷變化之中。字節碼文件的版本信息讓虛擬機知道如何去讀取並處理該字節碼文件。
- Constant Pool:該項存放了類中各種文字字符串、類名、方法名和接口名稱、final 變量以及對外部類的引用信息等常量。虛擬機必須為每一個被裝載的類維護一個常量池,常量池中存儲了相應類型所用到的所有類型、字段和方法的符號引用,因此它在 Java 的動態鏈接中起到了核心的作用。常量池的大小平均占到了整個類大小的 60% 左右。
- Access_flag:該項指明了該文件中定義的是類還是接口(一個 class 文件中只能有一個類或接口),同時還指名了類或接口的訪問標志,如 public,private, abstract 等信息。
- This Class:指向表示該類全限定名稱的字符串常量的指針。
- Super Class:指向表示父類全限定名稱的字符串常量的指針。
- Interfaces:一個指針數組,存放了該類或父類實現的所有接口名稱的字符串常量的指針。以上三項所指向的常量,特別是前兩項,在我們用 ASM 從已有類派生新類時一般需要修改:將類名稱改為子類名稱;將父類改為派生前的類名稱;如果有必要,增加新的實現接口。
- Fields:該項對類或接口中聲明的字段進行了細致的描述。需要注意的是,fields 列表中僅列出了本類或接口中的字段,並不包括從超類和父接口繼承而來的字段。
- Methods:該項對類或接口中聲明的方法進行了細致的描述。例如方法的名稱、參數和返回值類型等。需要注意的是,methods 列表里僅存放了本類或本接口中的方法,並不包括從超類和父接口繼承而來的方法。使用 ASM 進行 AOP 編程,通常是通過調整 Method 中的指令來實現的。
- Class attributes:該項存放了在該文件中類或接口所定義的屬性的基本信息。
事實上,使用 ASM 動態生成類,不需要像早年的 class hacker 一樣,熟知 class 文件的每一段,以及它們的功能、長度、偏移量以及編碼方式。ASM 會給我們照顧好這一切的,我們只要告訴 ASM 要改動什么就可以了 —— 當然,我們首先得知道要改什么:對字節碼文件格式了解的越多,我們就能更好地使用 ASM 這個利器。
ASM 提供了兩種編程模型:
- Core API,提供了基於事件形式的編程模型。該模型不需要一次性將整個類的結構讀取到內存中,因此這種方式更快,需要更少的內存。但這種編程方式難度較大。
- Tree API,提供了基於樹形的編程模型。該模型需要一次性將一個類的完整結構全部讀取到內存當中,所以這種方法需要更多的內存。這種編程方式較簡單。
Core API 中操縱字節碼的功能基於 ClassVisitor 接口。這個接口中的每個方法對應了 class 文件中的每一項。Class 文件中的簡單項的訪問使用一個單獨的方法,方法參數描述了這個項的內容。而那些具有任意長度和復雜度的項,使用另外一類方法,這類方法會返回一個輔助的 Visitor 接口,通過這些輔助接口的對象來完成具體內容的訪問。例如 visitField 方法和 visitMethod 方法,分別返回 FieldVisitor 和 MethodVisitor 接口的對象。
ASM 提供了三個基於 ClassVisitor 接口的類來實現 class 文件的生成和轉換:
- ClassReader:ClassReader 解析一個類的 class 字節碼,該類的 accept 方法接受一個 ClassVisitor 的對象,在 accept 方法中,會按上文描述的順序逐個調用 ClassVisitor 對象的方法。它可以被看做事件的生產者。
- ClassAdapter:ClassAdapter 是 ClassVisitor 的實現類。它的構造方法中需要一個 ClassVisitor 對象,並保存為字段 protected ClassVisitor cv。在它的實現中,每個方法都是原封不動的直接調用 cv 的對應方法,並傳遞同樣的參數。可以通過繼承 ClassAdapter 並修改其中的部分方法達到過濾的作用。它可以看做是事件的過濾器。
- ClassWriter:ClassWriter 也是 ClassVisitor 的實現類。ClassWriter 可以用來以二進制的方式創建一個類的字節碼。對於 ClassWriter 的每個方法的調用會創建類的相應部分。例如:調用 visit 方法就是創建一個類的聲明部分,每調用一次 visitMethod 方法就會在這個類中創建一個新的方法。在調用 visitEnd 方法后即表明該類的創建已經完成。它最終生成一個字節數組,這個字節數組中包含了一個類的 class 文件的完整字節碼內容 。可以通過 toByteArray 方法獲取生成的字節數組。ClassWriter 可以看做事件的消費者。
通常情況下,它們是組合起來使用的。
項目結構如下:
HelloWorld.java代碼如下:
- package net.oseye.demoasm;
- public class HelloWorld {
- public void sayHello() {
- System.out.println("Hello World!");
- }
- }
如果我們想動態地在HelloWorld.java的sayHello方法中加入打印時間如:
- package net.oseye.demoasm;
- public class HelloWorld {
- public void sayHello() {
- System.out.println(System.currentTimeMillis());
- System.out.println("Hello World!");
- }
- }
怎么做呢?
直接編碼ASM其實對於新手來說是很困難的事,但幸運的是ASM給我們提供了ASMifer工具。一般我們會使用ASM的ASMifer工具生成ASM結構來對比,使用命令:
- java org.objectweb.asm.util.ASMifier net.oseye.demoasm.HelloWorld
記得"asm-util-x.x.jar"需要在classpath中,如果沒有記得設置classpath,生成沒加入打印時間的HelloWorld.Class的ASM結構如下:
- package asm.net.oseye.demoasm;
- import java.util.*;
- import org.objectweb.asm.*;
- import org.objectweb.asm.attrs.*;
- public class HelloWorldDump implements Opcodes {
- public static byte[] dump () throws Exception {
- ClassWriter cw = new ClassWriter(0);
- FieldVisitor fv;
- MethodVisitor mv;
- AnnotationVisitor av0;
- cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "net/oseye/demoasm/HelloWorld", null, "ja
- va/lang/Object", null);
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "sayHello", "()V", null, null);
- mv.visitCode();
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")
- ;
- mv.visitLdcInsn("Hello World!");
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang
- /String;)V");
- mv.visitInsn(RETURN);
- mv.visitMaxs(2, 1);
- mv.visitEnd();
- }
- cw.visitEnd();
- return cw.toByteArray();
- }
- }
而加入打印時間的ASM結構如下:
- package asm.net.oseye.demoasm;
- import java.util.*;
- import org.objectweb.asm.*;
- import org.objectweb.asm.attrs.*;
- public class HelloWorldDump implements Opcodes {
- public static byte[] dump () throws Exception {
- ClassWriter cw = new ClassWriter(0);
- FieldVisitor fv;
- MethodVisitor mv;
- AnnotationVisitor av0;
- cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, "net/oseye/demoasm/HelloWorld", null, "ja
- va/lang/Object", null);
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "sayHello", "()V", null, null);
- mv.visitCode();
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")
- ;
- mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J")
- ;
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V");
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")
- ;
- mv.visitLdcInsn("Hello World!");
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang
- /String;)V");
- mv.visitInsn(RETURN);
- mv.visitMaxs(3, 1);
- mv.visitEnd();
- }
- cw.visitEnd();
- return cw.toByteArray();
- }
- }
對比我們發現后者比前者多了:
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")
- ;
- mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J")
- ;
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V");
因此App.java可以這樣編碼:
- package net.oseye.demoasm;
- import java.io.IOException;
- import java.lang.reflect.InvocationTargetException;
- import org.objectweb.asm.ClassReader;
- import org.objectweb.asm.ClassVisitor;
- import org.objectweb.asm.ClassWriter;
- import org.objectweb.asm.MethodVisitor;
- import org.objectweb.asm.Opcodes;
- public class App extends ClassLoader implements Opcodes {
- public static void main(String[] args) throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, InstantiationException {
- ClassReader cr=new ClassReader(HelloWorld.class.getName());
- ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS);
- CustomVisitor myv=new CustomVisitor(Opcodes.ASM4,cw);
- cr.accept(myv, 0);
- byte[] code=cw.toByteArray();
- //自定義加載器
- App loader=new App();
- Class<?> appClass=loader.defineClass(null, code, 0,code.length);
- appClass.getMethods()[0].invoke(appClass.newInstance(), new Object[]{});
- // FileOutputStream f=new FileOutputStream(new File("d:"+File.separator+"ok2.class"));
- // f.write(code);;
- // f.close();
- }
- }
- /**
- * ClassVisitor的實現類
- * App.java:demoasm
- * Jul 17, 2014
- * @author kevin.zhai
- */
- class CustomVisitor extends ClassVisitor implements Opcodes {
- public CustomVisitor(int api, ClassVisitor cv) {
- super(api, cv);
- }
- @Override
- public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
- MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
- if (name.equals("sayHello")) {
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(J)V");
- }
- return mv;
- }
- }
運行可以看到類似這樣的輸出:
1405587042484
Hello World!
當然你也可以把通過ASM生成的class保存到磁盤然后加載。
PS:
- 參考資料匯總:
AOP 的利器:ASM 3.0 介紹
使用 ASM 實現 Java 語言的“多重繼承”
ASM3 0指南翻譯
Java字節碼(.class文件)格式詳解
JVM指令集(指令碼、助記符、功能描述)
jvm指令集理解
asm4-guide - JVM詳細指令
指令碼
助記符
功能描述
0x00
nop
無操作
0x01
aconst_null
指令格式: aconst_null
功能描述: null進棧。
指令執行前
指令執行后
棧底
...
...
null
棧頂
注意:JVM並沒有為null指派一個具體的值。
0x02
iconst_m1
int型常量值-1進棧
0x03
iconst_0
int型常量值0進棧
0x04
iconst_1
int型常量值1進棧
0x05
iconst_2
int型常量值2進棧
0x06
iconst_3
int型常量值3進棧
0x07
iconst_4
int型常量值4進棧
0x08
iconst_5
int型常量值5進棧
0x09
lconst_0
long型常量值0進棧
0x0A
lconst_1
long型常量值1進棧
0x0B
fconst_0
float型常量值0進棧
0x0C
fconst_1
float型常量值1進棧
0x0D
fconst_2
float型常量值2進棧
0x0E
dconst_0
double型常量值0進棧
0x0F
dconst_1
double型常量值1進棧
0x10
bipush
將一個byte型常量值推送至棧頂
0x11
sipush
將一個short型常量值推送至棧頂
0x12
ldc
將int、float或String型常量值從常量池中推送至棧頂
0x13
ldc_w
將int、float或String型常量值從常量池中推送至棧頂(寬索引)
0x14
ldc2_w
將long或double型常量值從常量池中推送至棧頂(寬索引)
0x15
iload
指定的int型局部變量進棧
0x16
lload
指定的long型局部變量進棧
0x17
fload
指定的float型局部變量進棧
0x18
dload
指定的double型局部變量進棧
0x19
aload
指令格式: aload index
功能描述: 當前frame的局部變量數組中下標為
index的引用型局部變量進棧
指令執行前
指令執行后
棧底
...
...
objectref
棧頂
index : 無符號一byte整型。和wide指令聯用,
可以使index為兩byte。
0x1A
iload_0
第一個int型局部變量進棧
0x1B
iload_1
第二個int型局部變量進棧
0x1C
iload_2
第三個int型局部變量進棧
0x1D
iload_3
第四個int型局部變量進棧
0x1E
lload_0
第一個long型局部變量進棧
0x1F
lload_1
第二個long型局部變量進棧
0x20
lload_2
第三個long型局部變量進棧
0x21
lload_3
第四個long型局部變量進棧
0x22
fload_0
第一個float型局部變量進棧
0x23
fload_1
第二個float型局部變量進棧
0x24
fload_2
第三個float型局部變量進棧
0x25
fload_3
第四個float型局部變量進棧
0x26
dload_0
第一個double型局部變量進棧
0x27
dload_1
第二個double型局部變量進棧
0x28
dload_2
第三個double型局部變量進棧
0x29
dload_3
第四個double型局部變量進棧
0x2A
aload_0
指令格式:aload_0
該指令的行為類似於aload指令index為0的情況。
0x2B
aload_1
同上
0x2C
aload_2
同上
0x2D
aload_3
同上
0x2E
iaload
指定的int型數組的指定下標處的值進棧
0x2F
laload
指定的long型數組的指定下標處的值進棧
0x30
faload
指定的float型數組的指定下標處的值進棧
0x31
daload
指定的double型數組的指定下標處的值進棧
0x32
aaload
指令格式: aaload
功能描述: 棧頂的數組下標(index)、數組引用
(arrayref)出棧,並根據這兩個數值
取出對應的數組元素值(value)進棧。
拋出異常: 如果arrayref的值為null,會拋出
NullPointerException。
如果index造成數組越界,會拋出
ArrayIndexOutOfBoundsException。
指令執行前
指令執行后
棧底
...
...
arrayref
value
index
棧頂
index : int類型
arrayref : 數組的引用
0x33
baload
指定的boolean或byte型數組的指定下標處的值進棧
0x34
caload
指定的char型數組的指定下標處的值進棧
0x35
saload
指定的short型數組的指定下標處的值進棧
0x36
istore
將棧頂int型數值存入指定的局部變量
0x37
lstore
將棧頂long型數值存入指定的局部變量
0x38
fstore
將棧頂float型數值存入指定的局部變量
0x39
dstore
將棧頂double型數值存入指定的局部變量
0x3A
astore
指令格式: astore index
功能描述: 將棧頂數值(objectref)存入當前
frame的局部變量數組中指定下標
(index)處的變量中,棧頂數值出棧。
指令執行前
指令執行后
棧底
...
...
objectref
棧頂
index : 無符號一byte整數。該指令和wide聯
用,index可以為無符號兩byte整數。
0x3B
istore_0
將棧頂int型數值存入第一個局部變量
0x3C
istore_1
將棧頂int型數值存入第二個局部變量
0x3D
istore_2
將棧頂int型數值存入第三個局部變量
0x3E
istore_3
將棧頂int型數值存入第四個局部變量
0x3F
lstore_0
將棧頂long型數值存入第一個局部變量
0x40
lstore_1
將棧頂long型數值存入第二個局部變量
0x41
lstore_2
將棧頂long型數值存入第三個局部變量
0x42
lstore_3
將棧頂long型數值存入第四個局部變量
0x43
fstore_0
將棧頂float型數值存入第一個局部變量
0x44
fstore_1
將棧頂float型數值存入第二個局部變量
0x45
fstore_2
將棧頂float型數值存入第三個局部變量
0x46
fstore_3
將棧頂float型數值存入第四個局部變量
0x47
dstore_0
將棧頂double型數值存入第一個局部變量
0x48
dstore_1
將棧頂double型數值存入第二個局部變量
0x49
dstore_2
將棧頂double型數值存入第三個局部變量
0x4A
dstore_3
將棧頂double型數值存入第四個局部變量
0x4B
astore_0
指令格式: astore_0
功能描述: 該指令的行為類似於astore指令index
為0的情況。
0x4C
astore_1
同上
0x4D
astore_2
同上
0x4E
astore_3
同上
0x4F
iastore
將棧頂int型數值存入指定數組的指定下標處
0x50
lastore
將棧頂long型數值存入指定數組的指定下標處
0x51
fastore
將棧頂float型數值存入指定數組的指定下標處
0x52
dastore
將棧頂double型數值存入指定數組的指定下標處
0x53
aastore
指令格式: aastore
功能描述: 根據棧頂的引用型數值(value)、數組下
標(index)、數組引用(arrayref)出
棧,將數值存入對應的數組元素中。
拋出異常: 如果value的類型和arrayref所引用
的數組的元素類型不兼容,會拋出拋出
ArrayStoreException。
如果index造成數組越界,會拋出
ArrayIndexOutOfBoundsException。
如果arrayref值為null,會拋出
NullPointerException。
指令執行前
指令執行后
棧底
...
...
arrayref
index
value
棧頂
arrayref : 必須是對數組的引用
index : int類型
value : 引用類型
0x54
bastore
將棧頂boolean或byte型數值存入指定數組的指定下標處
0x55
castore
將棧頂char型數值存入指定數組的指定下標處
0x56
sastore
將棧頂short型數值存入指定數組的指定下標處
0x57
pop
棧頂數值出棧 (該棧頂數值不能是long或double型)
0x58
pop2
棧頂的一個(如果是long、double型的)或兩個(其它類型的)數值出棧
0x59
dup
復制棧頂數值,並且復制值進棧
0x5A
dup_x1
復制棧頂數值,並且復制值進棧2次
0x5B
dup_x2
復制棧頂數值,並且復制值進棧2次或3次
0x5C
dup2
復制棧頂一個(long、double型的)或兩個(其它類型的)數值,並且復制值進棧
0x5D
dup2_x1
0x5E
dup2_x2
0x5F
swap
棧頂的兩個數值互換(要求棧頂的兩個數值不能是long或double型的)
0x60
iadd
棧頂兩int型數值相加,並且結果進棧
0x61
ladd
棧頂兩long型數值相加,並且結果進棧
0x62
fadd
棧頂兩float型數值相加,並且結果進棧
0x63
dadd
棧頂兩double型數值相加,並且結果進棧
0x64
isub
棧頂兩int型數值相減,並且結果進棧
0x65
lsub
棧頂兩long型數值相減,並且結果進棧
0x66
fsub
棧頂兩float型數值相減,並且結果進棧
0x67
dsub
棧頂兩double型數值相減,並且結果進棧
0x68
imul
棧頂兩int型數值相乘,並且結果進棧
0x69
lmul
棧頂兩long型數值相乘,並且結果進棧
0x6A
fmul
棧頂兩float型數值相乘,並且結果進棧
0x6B
dmul
棧頂兩double型數值相乘,並且結果進棧
0x6C
idiv
棧頂兩int型數值相除,並且結果進棧
0x6D
ldiv
棧頂兩long型數值相除,並且結果進棧
0x6E
fdiv
棧頂兩float型數值相除,並且結果進棧
0x6F
ddiv
棧頂兩double型數值相除,並且結果進棧
0x70
irem
棧頂兩int型數值作取模運算,並且結果進棧
0x71
lrem
棧頂兩long型數值作取模運算,並且結果進棧
0x72
frem
棧頂兩float型數值作取模運算,並且結果進棧
0x73
drem
棧頂兩double型數值作取模運算,並且結果進棧
0x74
ineg
棧頂int型數值取負,並且結果進棧
0x75
lneg
棧頂long型數值取負,並且結果進棧
0x76
fneg
棧頂float型數值取負,並且結果進棧
0x77
dneg
棧頂double型數值取負,並且結果進棧
0x78
ishl
int型數值左移指定位數,並且結果進棧
0x79
lshl
long型數值左移指定位數,並且結果進棧
0x7A
ishr
int型數值帶符號右移指定位數,並且結果進棧
0x7B
lshr
long型數值帶符號右移指定位數,並且結果進棧
0x7C
iushr
int型數值無符號右移指定位數,並且結果進棧
0x7D
lushr
long型數值無符號右移指定位數,並且結果進棧
0x7E
iand
棧頂兩int型數值按位與,並且結果進棧
0x7F
land
棧頂兩long型數值按位與,並且結果進棧
0x80
ior
棧頂兩int型數值按位或,並且結果進棧
0x81
lor
棧頂兩long型數值按位或,並且結果進棧
0x82
ixor
棧頂兩int型數值按位異或,並且結果進棧
0x83
lxor
棧頂兩long型數值按位異或,並且結果進棧
0x84
iinc
指定int型變量增加指定值
0x85
i2l
棧頂int值強轉long值,並且結果進棧
0x86
i2f
棧頂int值強轉float值,並且結果進棧
0x87
i2d
棧頂int值強轉double值,並且結果進棧
0x88
l2i
棧頂long值強轉int值,並且結果進棧
0x89
l2f
棧頂long值強轉float值,並且結果進棧
0x8A
l2d
棧頂long值強轉double值,並且結果進棧
0x8B
f2i
棧頂float值強轉int值,並且結果進棧
0x8C
f2l
棧頂float值強轉long值,並且結果進棧
0x8D
f2d
棧頂float值強轉double值,並且結果進棧
0x8E
d2i
棧頂double值強轉int值,並且結果進棧
0x8F
d2l
棧頂double值強轉long值,並且結果進棧
0x90
d2f
棧頂double值強轉float值,並且結果進棧
0x91
i2b
棧頂int值強轉byte值,並且結果進棧
0x92
i2c
棧頂int值強轉char值,並且結果進棧
0x93
i2s
棧頂int值強轉short值,並且結果進棧
0x94
lcmp
比較棧頂兩long型數值大小,並且結果(1,0,-1)進棧
0x95
fcmpl
比較棧頂兩float型數值大小,並且結果(1,0,-1)進棧;當其中一個數值為NaN時, -1進棧
0x96
fcmpg
比較棧頂兩float型數值大小,並且結果(1,0,-1)進棧;當其中一個數值為NaN時,1進棧
0x97
dcmpl
比較棧頂兩double型數值大小,並且結果(1,0,-1)進棧;當其中一個數值為NaN時,-1進棧
0x98
dcmpg
比較棧頂兩double型數值大小,並且結果(1,0,-1)進棧;當其中一個數值為NaN時,1進棧
0x99
ifeq
當棧頂int型數值等於0時跳轉
0x9A
ifne
當棧頂int型數值不等於0時跳轉
0x9B
iflt
當棧頂int型數值小於0時跳轉
0x9C
ifge
當棧頂int型數值大於等於0時跳轉
0x9D
ifgt
當棧頂int型數值大於0時跳轉
0x9E
ifle
當棧頂int型數值小於等於0時跳轉
0x9F
if_icmpeq
比較棧頂兩int型數值大小,當結果等於0時跳轉
0xA0
if_icmpne
比較棧頂兩int型數值大小,當結果不等於0時跳轉
0xA1
if_icmplt
比較棧頂兩int型數值大小,當結果小於0時跳轉
0xA2
if_icmpge
比較棧頂兩int型數值大小,當結果大於等於0時跳轉
0xA3
if_icmpgt
比較棧頂兩int型數值大小,當結果大於0時跳轉
0xA4
if_icmple
比較棧頂兩int型數值大小,當結果小於等於0時跳轉
0xA5
if_acmpeq
比較棧頂兩引用型數值,當結果相等時跳轉
0xA6
if_acmpne
比較棧頂兩引用型數值,當結果不相等時跳轉
0xA7
goto
無條件跳轉
0xA8
jsr
跳轉至指定16位offset位置,並將jsr下一條指令地址壓入棧頂
0xA9
ret
返回至局部變量指定的index的指令位置(通常與jsr、jsr_w聯合使用)
0xAA
tableswitch
用於switch條件跳轉,case值連續(可變長度指令)
0xAB
lookupswitch
用於switch條件跳轉,case值不連續(可變長度指令)
0xAC
ireturn
當前方法返回int
0xAD
lreturn
當前方法返回long
0xAE
freturn
當前方法返回float
0xAF
dreturn
當前方法返回double
0xB0
areturn
指令格式: areturn
功能描述: 從方法中返回一個對象的引用。
拋出異常: 如果當前方法是synchronized方法,
並且當前線程不是改方法的鎖的擁有者,
會拋出
IllegalMonitorStateException。
指令執行前
指令執行后
棧底
...
objectref
棧頂
objectref : 被返回的對象引用。
0xB1
return
當前方法返回void
0xB2
getstatic
獲取指定類的靜態域,並將其值壓入棧頂
0xB3
putstatic
為指定的類的靜態域賦值
0xB4
getfield
獲取指定類的實例域,並將其值壓入棧頂
0xB5
putfield
為指定的類的實例域賦值
0xB6
invokevirtual
調用實例方法
0xB7
invokespecial
調用超類構造方法、實例初始化方法、私有方法
0xB8
invokestatic
調用靜態方法
0xb9
invokeinterface
調用接口方法
0xBA
---
因為歷史原因,該碼點為未使用的保留碼點
0xBB
new
創建一個對象,並且其引用進棧
0xBC
newarray
創建一個基本類型數組,並且其引用進棧
0xBD
anewarray
指令格式: anewarray index1 index2
功能描述: 棧頂數值(count)作為數組長度,創建
一個引用 型數組。棧頂數值出棧,數組引
用進棧。
拋出異常: 如果count小於0,會拋出
NegativeArraySizeException
指令執行前
指令執行后
棧底
...
...
count
arrayref
棧頂
count : int類型。
arrayref : 對所創建的數組的引用。
0xBE
arraylength
指令格式: arraylength
功能描述: 棧頂的數組引用(arrayref)出棧,該
數組的長度進棧。
拋出異常: 如果arrayref的值為null,會拋出
NullPointerException。
指令執行前
指令執行后
棧底
...
...
arrayref
length
棧頂
arrayref : 數組引用
length : 數組長度
0xBF
athrow
指令格式: athrow
功能描述: 將棧頂的數值作為異常或錯誤拋出
拋出異常: 如果棧頂數值為null,則使用
NullPointerException代替棧頂數
值拋出。
如果方法是的,則有可能拋出
IllegalMonitorStateException。
指令執行前
指令執行后
棧底
...
objectref
objectref
棧頂
objectref : Throwable或其子類的實例的引用。
0xC0
checkcast
類型轉換檢查,如果該檢查未通過將會拋出ClassCastException異常
0xc1
instanceof
檢查對象是否是指定的類的實例。如果是,1進棧;否則,0進棧
0xC2
monitorenter
獲得對象鎖
0xC3
monitorexit
釋放對象鎖
0xC4
wide
用於修改其他指令的行為
0xC5
multianewarray
創建指定類型和維度的多維數組(執行該指令時,棧中必須包含各維度的長度值),並且其引用值進棧
0xC6
ifnull
為null時跳轉
0xC7
ifnonnull
不為null時跳轉
0xC8
goto_w
無條件跳轉(寬索引)
0xC9
jsr_w
跳轉至指定32位offset位置,並且jsr_w下一條指令地址進棧
0xCA
breakpoint
0xFE
impdep1
0xFF
impdep2