學(xué)會Java字節(jié)碼指令,成為技術(shù)大佬
Java 官方的虛擬機(jī) Hotspot 是基于棧的,而不是基于寄存器的。
基于棧的優(yōu)點(diǎn)是可移植性更好、指令更短、實(shí)現(xiàn)起來簡單,但不能隨機(jī)訪問棧中的元素,完成相同功能所需要的指令數(shù)也比寄存器的要多,需要頻繁的入棧和出棧。
基于寄存器的優(yōu)點(diǎn)是速度快,有利于程序運(yùn)行速度的優(yōu)化,但操作數(shù)需要顯式指定,指令也比較長。
Java 字節(jié)碼由操作碼和操作數(shù)組成。
- 操作碼(Opcode):一個(gè)字節(jié)長度(0-255,意味著指令集的操作碼總數(shù)不可能超過 256 條),代表著某種特定的操作含義。
- 操作數(shù)(Operands):零個(gè)或者多個(gè),緊跟在操作碼之后,代表此操作需要的參數(shù)。
由于 Java 虛擬機(jī)是基于棧而不是寄存器的結(jié)構(gòu),所以大多數(shù)指令都只有一個(gè)操作碼。比如aload_0(將局部變量表中下標(biāo)為 0 的數(shù)據(jù)壓入操作數(shù)棧中)就只有操作碼沒有操作數(shù),而 invokespecial #1(調(diào)用成員方法或者構(gòu)造方法,并傳遞常量池中下標(biāo)為 1 的常量)就是由操作碼和操作數(shù)組成的。
01、加載與存儲指令
加載(load)和存儲(store)相關(guān)的指令是使用最頻繁的指令,用于將數(shù)據(jù)從棧幀的局部變量表和操作數(shù)棧之間來回傳遞。
1)將局部變量表中的變量壓入操作數(shù)棧中
- xload_(x 為 i、l、f、d、a,n 默認(rèn)為 0 到 3),表示將第 n 個(gè)局部變量壓入操作數(shù)棧中。
- xload(x 為 i、l、f、d、a),通過指定參數(shù)的形式,將局部變量壓入操作數(shù)棧中,當(dāng)使用這個(gè)指令時(shí),表示局部變量的數(shù)量可能超過了 4 個(gè)
解釋一下
x 為操作碼助記符,表明是哪一種數(shù)據(jù)類型。見下表所示。

像 arraylength 指令,沒有操作碼助記符,它沒有代表數(shù)據(jù)類型的特殊字符,但操作數(shù)只能是一個(gè)數(shù)組類型的對象。
大部分的指令都不支持 byte、short 和 char,甚至沒有任何指令支持 boolean 類型。編譯器會將 byte 和 short 類型的數(shù)據(jù)帶符號擴(kuò)展(Sign-Extend)為 int 類型,將 boolean 和 char 零位擴(kuò)展(Zero-Extend)為 int 類型。
舉例來說:
private void load(int age, String name, long birthday, boolean sex) {
System.out.println(age + name + birthday + sex);
}
通過 jclasslib 看一下 load() 方法(4 個(gè)參數(shù))的字節(jié)碼指令。

- iload_1:將局部變量表中下標(biāo)為 1 的 int 變量壓入操作數(shù)棧中。
- aload_2:將局部變量表中下標(biāo)為 2 的引用數(shù)據(jù)類型變量(此時(shí)為 String)壓入操作數(shù)棧中。
- lload_3:將局部變量表中下標(biāo)為 3 的 long 型變量壓入操作數(shù)棧中。
- iload 5:將局部變量表中下標(biāo)為 5 的 int 變量(實(shí)際為 boolean)壓入操作數(shù)棧中。
通過查看局部變量表就能關(guān)聯(lián)上了。

2)將常量池中的常量壓入操作數(shù)棧中
根據(jù)數(shù)據(jù)類型和入棧內(nèi)容的不同,此類又可以細(xì)分為 const 系列、push 系列和 Idc 指令。
const 系列,用于特殊的常量入棧,要入棧的常量隱含在指令本身。

push 系列,主要包括 bipush 和 sipush,前者接收 8 位整數(shù)作為參數(shù),后者接收 16 位整數(shù)。
Idc 指令,當(dāng) const 和 push 不能滿足的時(shí)候,萬能的 Idc 指令就上場了,它接收一個(gè) 8 位的參數(shù),指向常量池中的索引。
- Idc_w:接收兩個(gè) 8 位數(shù),索引范圍更大。
- 如果參數(shù)是 long 或者 double,使用 Idc2_w 指令。
舉例來說:
public void pushConstLdc() {
// 范圍 [-1,5]
int iconst = -1;
// 范圍 [-128,127]
int bipush = 127;
// 范圍 [-32768,32767]
int sipush= 32767;
// 其他 int
int ldc = 32768;
String aconst = null;
String IdcString = "沉默王二";
}
通過 jclasslib 看一下 pushConstLdc() 方法的字節(jié)碼指令。

- iconst_m1:將 -1 入棧。范圍 [-1,5]。
- bipush 127:將 127 入棧。范圍 [-128,127]。
- sipush 32767:將 32767 入棧。范圍 [-32768,32767]。
- ldc #6 <32768>:將常量池中下標(biāo)為 6 的常量 32768 入棧。
- aconst_null:將 null 入棧。
- ldc #7 <沉默王二>:將常量池中下標(biāo)為 7 的常量“沉默王二”入棧。
3)將棧頂?shù)臄?shù)據(jù)出棧并裝入局部變量表中
主要是用來給局部變量賦值,這類指令主要以 store 的形式存在。
- xstore_(x 為 i、l、f、d、a,n 默認(rèn)為 0 到 3)
- xstore(x 為 i、l、f、d、a)
明白了 xload_ 和 xload,再看 xstore_ 和 xstore 就會輕松得多,作用反了一下而已。
大家來想一個(gè)問題,為什么要有 xstore_ 和 xload_ 呢?它們的作用和 xstore n、xload n 不是一樣的嗎?
xstore_ 和 xstore n 的區(qū)別在于,前者相當(dāng)于只有操作碼,占用 1 個(gè)字節(jié);后者相當(dāng)于由操作碼和操作數(shù)組成,操作碼占 1 個(gè)字節(jié),操作數(shù)占 2 個(gè)字節(jié),一共占 3 個(gè)字節(jié)。
由于局部變量表中前幾個(gè)位置總是非常常用,雖然 xstore_<n> 和 xload_<n> 增加了指令數(shù)量,但字節(jié)碼的體積變小了!
舉例來說:
public void store(int age, String name) {
int temp = age + 2;
String str = name;
}
通過 jclasslib 看一下 store() 方法的字節(jié)碼指令。

- istore_3:從操作數(shù)中彈出一個(gè)整數(shù),并把它賦值給局部變量表中索引為 3 的變量。
- astore 4:從操作數(shù)中彈出一個(gè)引用數(shù)據(jù)類型,并把它賦值給局部變量表中索引為 4 的變量。
通過查看局部變量表就能關(guān)聯(lián)上了。

02、算術(shù)指令
算術(shù)指令用于對兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算,并把結(jié)果重新壓入操作數(shù)棧。可以分為兩類:整型數(shù)據(jù)的運(yùn)算指令和浮點(diǎn)數(shù)據(jù)的運(yùn)算指令。
需要注意的是,數(shù)據(jù)運(yùn)算可能會導(dǎo)致溢出,比如兩個(gè)很大的正整數(shù)相加,很可能會得到一個(gè)負(fù)數(shù)。但 Java 虛擬機(jī)規(guī)范中并沒有對這種情況給出具體結(jié)果,因此程序是不會顯式報(bào)錯(cuò)的。所以,大家在開發(fā)過程中,如果涉及到較大的數(shù)據(jù)進(jìn)行加法、乘法運(yùn)算的時(shí)候,一定要注意!
當(dāng)發(fā)生溢出時(shí),將會使用有符號的無窮大 Infinity 來表示;如果某個(gè)操作結(jié)果沒有明確的數(shù)學(xué)定義的話,將會使用 NaN 值來表示。而且所有使用 NaN 作為操作數(shù)的算術(shù)操作,結(jié)果都會返回 NaN。
舉例來說:
public void infinityNaN() {
int i = 10;
double j = i / 0.0;
System.out.println(j); // Infinity
double d1 = 0.0;
double d2 = d1 / 0.0;
System.out.println(d2); // NaN
}
- 任何一個(gè)非零的數(shù)除以浮點(diǎn)數(shù) 0(注意不是 int 類型),可以想象結(jié)果是無窮大 Infinity 的。
- 把這個(gè)非零的數(shù)換成 0 的時(shí)候,結(jié)果又不太好定義,就用 NaN 值來表示。
Java 虛擬機(jī)提供了兩種運(yùn)算模式:
- 向最接近數(shù)舍入:在進(jìn)行浮點(diǎn)數(shù)運(yùn)算時(shí),所有的結(jié)果都必須舍入到一個(gè)適當(dāng)?shù)木?,不是特別精確的結(jié)果必須舍入為可被表示的最接近的精確值,如果有兩種可表示的形式與該值接近,將優(yōu)先選擇最低有效位為零的(類似四舍五入)。
- 向零舍入:將浮點(diǎn)數(shù)轉(zhuǎn)換為整數(shù)時(shí),采用該模式,該模式將在目標(biāo)數(shù)值類型中選擇一個(gè)最接近但是不大于原值的數(shù)字作為最精確的舍入結(jié)果(類似取整)。
我把所有的算術(shù)指令列一下:
- 加法指令:iadd、ladd、fadd、dadd
- 減法指令:isub、lsub、fsub、dsub
- 乘法指令:imul、lmul、fmul、dmul
- 除法指令:idiv、ldiv、fdiv、ddiv
- 求余指令:irem、lrem、frem、drem
- 自增指令:iinc
舉例來說:
public void calculate(int age) {
int add = age + 1;
int sub = age - 1;
int mul = age * 2;
int div = age / 3;
int rem = age % 4;
age++;
age--;
}
通過 jclasslib 看一下 calculate() 方法的字節(jié)碼指令。

- iadd,加法
- isub,減法
- imul,乘法
- idiv,除法
- irem,取余
- iinc,自增的時(shí)候 +1,自減的時(shí)候 -1
03、類型轉(zhuǎn)換指令
可以分為兩種:
1)寬化,小類型向大類型轉(zhuǎn)換,比如 int–>long–>float–>double,對應(yīng)的指令有:i2l、i2f、i2d、l2f、l2d、f2d。
- 從 int 到 long,或者從 int 到 double,是不會有精度丟失的;
- 從 int、long 到 float,或者 long 到 double 時(shí),可能會發(fā)生精度丟失;
- 從 byte、char 和 short 到 int 的寬化類型轉(zhuǎn)換實(shí)際上是隱式發(fā)生的,這樣可以減少字節(jié)碼指令,畢竟字節(jié)碼指令只有 256 個(gè),占一個(gè)字節(jié)。
2)窄化,大類型向小類型轉(zhuǎn)換,比如從 int 類型到 byte、short 或者 char,對應(yīng)的指令有:i2b、i2s、i2c;從 long 到 int,對應(yīng)的指令有:l2i;從 float 到 int 或者 long,對應(yīng)的指令有:f2i、f2l;從 double 到 int、long 或者 float,對應(yīng)的指令有:d2i、d2l、d2f。
窄化很可能會發(fā)生精度丟失,畢竟是不同的數(shù)量級;
但 Java 虛擬機(jī)并不會因此拋出運(yùn)行時(shí)異常。
舉例來說:
public void updown() {
int i = 10;
double d = i;
float f = 10f;
long ong = (long)f;
}
通過 jclasslib 看一下 updown() 方法的字節(jié)碼指令。

- i2d,int 寬化為 doublef
- 2l, float 窄化為 long
04、對象的創(chuàng)建和訪問指令
Java 是一門面向?qū)ο蟮木幊陶Z言,那么 Java 虛擬機(jī)是如何從字節(jié)碼層面進(jìn)行支持的呢?
1)創(chuàng)建指令
數(shù)組也是一種對象,但它創(chuàng)建的字節(jié)碼指令和普通的對象不同。創(chuàng)建數(shù)組的指令有三種:
- newarray:創(chuàng)建基本數(shù)據(jù)類型的數(shù)組
- anewarray:創(chuàng)建引用類型的數(shù)組
- multianewarray:創(chuàng)建多維數(shù)組
普通對象的創(chuàng)建指令只有一個(gè),就是 new,它會接收一個(gè)操作數(shù),指向常量池中的一個(gè)索引,表示要創(chuàng)建的類型。
舉例來說:
public void newObject() {
String name = new String("沉默王二");
File file = new File("無愁河的浪蕩漢子.book");
int [] ages = {};
}
通過 jclasslib 看一下 newObject() 方法的字節(jié)碼指令。

- new #13 <java/lang/String>,創(chuàng)建一個(gè) String 對象。
- new #15 <java/io/File>,創(chuàng)建一個(gè) File 對象。
- newarray 10 (int),創(chuàng)建一個(gè) int 類型的數(shù)組。
2)字段訪問指令
字段可以分為兩類,一類是成員變量,一類是靜態(tài)變量(static 關(guān)鍵字修飾的),所以字段訪問指令可以分為兩類:
- 訪問靜態(tài)變量:getstatic、putstatic。
- 訪問成員變量:getfield、putfield,需要創(chuàng)建對象后才能訪問。
舉例來說:
public class Writer {
private String name;
static String mark = "作者";
public static void main(String[] args) {
print(mark);
Writer w = new Writer();
print(w.name);
}
public static void print(String arg) {
System.out.println(arg);
}
}
通過 jclasslib 看一下 main() 方法的字節(jié)碼指令。

- getstatic #2 <com/itwanger/jvm/Writer.mark>,訪問靜態(tài)變量 mark
- getfield #6 <com/itwanger/jvm/Writer.name>,訪問成員變量 name
05、方法調(diào)用和返回指令
方法調(diào)用指令有 5 個(gè),分別用于不同的場景:
- invokevirtual:用于調(diào)用對象的成員方法,根據(jù)對象的實(shí)際類型進(jìn)行分派,支持多態(tài)。
- invokeinterface:用于調(diào)用接口方法,會在運(yùn)行時(shí)搜索由特定對象實(shí)現(xiàn)的接口方法進(jìn)行調(diào)用。
- invokespecial:用于調(diào)用一些需要特殊處理的方法,包括構(gòu)造方法、私有方法和父類方法。
- invokestatic:用于調(diào)用靜態(tài)方法。
- invokedynamic:用于在運(yùn)行時(shí)動態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,并執(zhí)行。
舉例來說:
public class InvokeExamples {
private void run() {
List ls = new ArrayList();
ls.add("難頂");
ArrayList als = new ArrayList();
als.add("學(xué)不動了");
}
public static void print() {
System.out.println("invokestatic");
}
public static void main(String[] args) {
print();
InvokeExamples invoke = new InvokeExamples();
invoke.run();
}
}
我們用 javap -c InvokeExamples.class 來反編譯一下。
Compiled from "InvokeExamples.java"
public class com.itwanger.jvm.InvokeExamples {
public com.itwanger.jvm.InvokeExamples();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
private void run();
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String 難頂
11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: new #2 // class java/util/ArrayList
20: dup
21: invokespecial #3 // Method java/util/ArrayList."<init>":()V
24: astore_2
25: aload_2
26: ldc #6 // String 學(xué)不動了
28: invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
31: pop
32: return
public static void print();
Code:
0: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #9 // String invokestatic
5: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #11 // Method print:()V
3: new #12 // class com/itwanger/jvm/InvokeExamples
6: dup
7: invokespecial #13 // Method "<init>":()V
10: astore_1
11: aload_1
12: invokevirtual #14 // Method run:()V
15: return
}
InvokeExamples 類有 4 個(gè)方法,包括缺省的構(gòu)造方法在內(nèi)。
1)InvokeExamples() 構(gòu)造方法中
缺省的構(gòu)造方法內(nèi)部會調(diào)用超類 Object 的初始化構(gòu)造方法:
`invokespecial #1 // Method java/lang/Object."<init>":()V`
2)成員方法 run() 中
invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
由于 ls 變量的引用類型為接口 List,所以 ls.add() 調(diào)用的是 invokeinterface 指令,等運(yùn)行時(shí)再確定是不是接口 List 的實(shí)現(xiàn)對象 ArrayList 的 add() 方法。
invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
由于 als 變量的引用類型已經(jīng)確定為 ArrayList,所以 als.add() 方法調(diào)用的是 invokevirtual 指令。
3)main() 方法中
invokestatic #11 // Method print:()V
print() 方法是靜態(tài)的,所以調(diào)用的是 invokestatic 指令。
方法返回指令根據(jù)方法的返回值類型進(jìn)行區(qū)分,常見的返回指令見下圖。

06、操作數(shù)棧管理指令
常見的操作數(shù)棧管理指令有 pop、dup 和 swap。
- 將一個(gè)或兩個(gè)元素從棧頂彈出,并且直接廢棄,比如 pop,pop2;
- 復(fù)制棧頂?shù)囊粋€(gè)或兩個(gè)數(shù)值并將其重新壓入棧頂,比如 dup,dup2,dup_×1,dup2_×1,dup_×2,dup2_×2;
- 將棧最頂端的兩個(gè)槽中的數(shù)值交換位置,比如 swap。
這些指令不需要指明數(shù)據(jù)類型,因?yàn)槭前凑瘴恢脡喝牒蛷棾龅摹?/p>
舉例來說:
public class Dup {
int age;
public int incAndGet() {
return ++age;
}
}
通過 jclasslib 看一下 incAndGet() 方法的字節(jié)碼指令。

- aload_0:將 this 入棧。
- dup:復(fù)制棧頂?shù)?this。
- getfield #2:將常量池中下標(biāo)為 2 的常量加載到棧上,同時(shí)將一個(gè) this 出棧。
- iconst_1:將常量 1 入棧。
- iadd:將棧頂?shù)膬蓚€(gè)值相加后出棧,并將結(jié)果放回棧上。
- dup_x1:復(fù)制棧頂?shù)脑?,并將其插?this 下面。
- putfield #2: 將棧頂?shù)膬蓚€(gè)元素出棧,并將其賦值給字段 age。
- ireturn:將棧頂?shù)脑爻鰲7祷亍?/li>
07、控制轉(zhuǎn)移指令
控制轉(zhuǎn)移指令包括:
- 比較指令,比較棧頂?shù)膬蓚€(gè)元素的大小,并將比較結(jié)果入棧。
- 條件跳轉(zhuǎn)指令,通常和比較指令一塊使用,在條件跳轉(zhuǎn)指令執(zhí)行前,一般先用比較指令進(jìn)行棧頂元素的比較,然后進(jìn)行條件跳轉(zhuǎn)。
- 比較條件轉(zhuǎn)指令,類似于比較指令和條件跳轉(zhuǎn)指令的結(jié)合體,它將比較和跳轉(zhuǎn)兩個(gè)步驟合二為一。
- 多條件分支跳轉(zhuǎn)指令,專為 switch-case 語句設(shè)計(jì)的。
- 無條件跳轉(zhuǎn)指令,目前主要是 goto 指令。
1)比較指令
比較指令有:dcmpg,dcmpl、fcmpg、fcmpl、lcmp,指令的第一個(gè)字母代表的含義分別是 double、float、long。注意,沒有 int 類型。
對于 double 和 float 來說,由于 NaN 的存在,有兩個(gè)版本的比較指令。拿 float 來說,有 fcmpg 和 fcmpl,區(qū)別在于,如果遇到 NaN,fcmpg 會將 1 壓入棧,fcmpl 會將 -1 壓入棧。
舉例來說。
public void lcmp(long a, long b) {
if(a > b){}
}
通過 jclasslib 看一下 lcmp() 方法的字節(jié)碼指令。

lcmp 用于兩個(gè) long 型的數(shù)據(jù)進(jìn)行比較。
2)條件跳轉(zhuǎn)指令

這些指令都會接收兩個(gè)字節(jié)的操作數(shù),它們的統(tǒng)一含義是,彈出棧頂元素,測試它是否滿足某一條件,滿足的話,跳轉(zhuǎn)到對應(yīng)位置。
對于 long、float 和 double 類型的條件分支比較,會先執(zhí)行比較指令返回一個(gè)整形值到操作數(shù)棧中后再執(zhí)行 int 類型的條件跳轉(zhuǎn)指令。
對于 boolean、byte、char、short,以及 int,則直接使用條件跳轉(zhuǎn)指令來完成。
舉例來說。
public void fi() {
int a = 0;
if (a == 0) {
a = 10;
} else {
a = 20;
}
}
通過 jclasslib 看一下 fi() 方法的字節(jié)碼指令。

3 ifne 12 (+9) 的意思是,如果棧頂?shù)脑夭坏扔?0,跳轉(zhuǎn)到第 12(3+9)行 12 bipush 20。
3)比較條件轉(zhuǎn)指令

前綴“if_”后,以字符“i”開頭的指令針對 int 型整數(shù)進(jìn)行操作,以字符“a”開頭的指令表示對象的比較。
舉例來說:
public void compare() {
int i = 10;
int j = 20;
System.out.println(i > j);
}
通過 jclasslib 看一下 compare() 方法的字節(jié)碼指令。

11 if_icmple 18 (+7) 的意思是,如果棧頂?shù)膬蓚€(gè) int 類型的數(shù)值比較的話,如果前者小于后者時(shí)跳轉(zhuǎn)到第 18 行(11+7)。
4)多條件分支跳轉(zhuǎn)指令
主要有 tableswitch 和 lookupswitch,前者要求多個(gè)條件分支值是連續(xù)的,它內(nèi)部只存放起始值和終止值,以及若干個(gè)跳轉(zhuǎn)偏移量,通過給定的操作數(shù) index,可以立即定位到跳轉(zhuǎn)偏移量位置,因此效率比較高;后者內(nèi)部存放著各個(gè)離散的 case-offset 對,每次執(zhí)行都要搜索全部的 case-offset 對,找到匹配的 case 值,并根據(jù)對應(yīng)的 offset 計(jì)算跳轉(zhuǎn)地址,因此效率較低。
舉例來說:
public void switchTest(int select) {
int num;
switch (select) {
case 1:
num = 10;
break;
case 2:
case 3:
num = 30;
break;
default:
num = 40;
}
}
通過 jclasslib 看一下 switchTest() 方法的字節(jié)碼指令。

case 2 的時(shí)候沒有 break,所以 case 2 和 case 3 是連續(xù)的,用的是 tableswitch。如果等于 1,跳轉(zhuǎn)到 28 行;如果等于 2 和 3,跳轉(zhuǎn)到 34 行,如果是 default,跳轉(zhuǎn)到 40 行。
5)無條件跳轉(zhuǎn)指令
goto 指令接收兩個(gè)字節(jié)的操作數(shù),共同組成一個(gè)帶符號的整數(shù),用于指定指令的偏移量,指令執(zhí)行的目的就是跳轉(zhuǎn)到偏移量給定的位置處。
前面的例子里都出現(xiàn)了 goto 的身影,也很好理解。如果指令的偏移量特別大,超出了兩個(gè)字節(jié)的范圍,可以使用指令 goto_w,接收 4 個(gè)字節(jié)的操作數(shù)。
想要走得更遠(yuǎn),Java 字節(jié)碼這塊就必須得硬碰硬地吃透,希望這些分享可以幫助到大家~
路漫漫其修遠(yuǎn)兮,吾將上下而求索
除了以上這些指令,還有異常處理指令和同步控制指令,很多Java 方面的系列文章,例如 Java 核心語法、Java 集合框架、Java IO、Java 并發(fā)編程、Java 虛擬機(jī)等,持續(xù)更新中,希望大家多多關(guān)注支持腳本之家!
- Java之字節(jié)碼以及優(yōu)勢案例講解
- Java字節(jié)碼增強(qiáng)技術(shù)知識點(diǎn)詳解
- 詳解Java動態(tài)字節(jié)碼技術(shù)
- 詳解Java字節(jié)碼編程之非常好用的javassist
- 淺談javap命令拆解字節(jié)碼文件
- Java字節(jié)碼中jvm實(shí)例用法
- Javassist如何操作Java 字節(jié)碼
- Java中invokedynamic字節(jié)碼指令問題
- java獲取版本號及字節(jié)碼編譯版本方法示例
- java 獲取字節(jié)碼文件的幾種方法總結(jié)
- java 中如何獲取字節(jié)碼文件的相關(guān)內(nèi)容
- java字節(jié)碼框架ASM操作字節(jié)碼的方法淺析
- java字節(jié)碼框架ASM的深入學(xué)習(xí)
- Java 將字符串動態(tài)生成字節(jié)碼的實(shí)現(xiàn)方法
- 通過java字節(jié)碼分析學(xué)習(xí)對象初始化順序
- Java字節(jié)碼的增強(qiáng)技術(shù)
相關(guān)文章
SpringBoot +Vue開發(fā)考試系統(tǒng)的教程
這篇文章主要介紹了SpringBoot +Vue開發(fā)考試系統(tǒng),支持多種題型:選擇題、多選題、判斷題、填空題、綜合題以及數(shù)學(xué)公式。支持在線考試,教師在線批改試卷。本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-05-05
java圖片滑動驗(yàn)證(登錄驗(yàn)證)原理與實(shí)現(xiàn)方法詳解
這篇文章主要介紹了java圖片滑動驗(yàn)證(登錄驗(yàn)證)原理與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式詳細(xì)分析了java圖片滑動登錄驗(yàn)證的相關(guān)原理、實(shí)現(xiàn)方法與操作技巧,需要的朋友可以參考下2019-09-09
java實(shí)現(xiàn)圖片滑動驗(yàn)證(包含前端代碼)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)圖片滑動驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
一文詳解Spring事務(wù)的實(shí)現(xiàn)與本質(zhì)
這篇文章主要介紹了Spring中事務(wù)的兩種實(shí)現(xiàn)方式:聲明式事務(wù)、編程式事務(wù)以及他們的本質(zhì)。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04
Mybatis實(shí)戰(zhàn)教程之入門到精通(經(jīng)典)
MyBatis是支持普通SQL查詢,存儲過程和高級映射的優(yōu)秀持久層框架,通過本文給大家介紹Mybatis實(shí)戰(zhàn)教程之入門到精通,對mybatis實(shí)戰(zhàn)教程相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-01-01
SpringMVC中利用@InitBinder來對頁面數(shù)據(jù)進(jìn)行解析綁定的方法
本篇文章主要介紹了SpringMVC中利用@InitBinder來對頁面數(shù)據(jù)進(jìn)行解析綁定的方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-03-03

