類文件詳解
類文件介紹
Class 文件是一組以 8 位字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在 Class 文件之中,中間沒有添加任何
分隔符,這使得整個 Class 文件中存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。
當遇到需要佔用 8 位字節以上空間的數據項時,則會按照高位在前(Big-Endian)的方式分割成若干個 8 位字節進行存儲。
Class 文件只有兩種數據類型:無符號數和表
鏈接:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
類文件結構
魔數
Class文件版本
常量池
訪問標誌
類索引,父類索引,接口索引集合
字段表集合
方法表集合
屬性表集合
ClassFile {
u4 magic; // 魔法數字,表明當前文件是.class文件,固定0xCAFEBABE
u2 minor_version; // 分別爲Class文件的副版本和主版本
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]; // 各種屬性
}
常量池
CONSTANT_Methodref_info
用於記錄方法的信息
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
類索引,父類索引
字段表集合
字段表用於描述接口或者類中聲明的變量
字節碼介紹
字節碼與數據類型
• 在虛擬機的指令集中,大多數的指令包含了其操作所對應的數據類型信息
• iLoad:從局部變量表中加載int型數據到操作數棧
• 大多數指令包含類型信息
• 類型多,指令少
加載與存儲指令
• 加載和存儲指令用於將數據在棧幀中的局部變量表和操作數棧直接來回傳輸
• 將局部變量表加載到操作數棧: iload lload fload dload aload
• 將一個數值從操作數棧存儲到局部變量表:istore :lfda
• 將一個常量加載到操作數棧:bipush sipush ldc ldc_w ldc2_w
aconst_null iconst_m1 iconst
• 擴充局部變量表的訪問索引的指令:wide
package jvm;
public class Test1 {
public Test1() {}
public static void main(String[] args) {
int a = 2;
int b =400;
int c = a+b;
System.out.println(c);
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// 操作數棧的深度爲2
// stack=2, locals=4, args_size=1
// 0: iconst_2 #常量2壓棧
// 1: istore_1 #出棧保存到本地變量1裏面
// 2: sipush 400
// 5: istore_2 #出棧保存到本地變量2裏面
// 6: iload_1 #局部變量1壓棧
// 7: iload_2 #局部變量2壓棧
// 8: iadd #棧頂兩個元素相加 計算結果壓棧
// 9: istore_3 #出棧保存到局部變量3中
// 10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 13: iload_3
// 14: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
// 17: return
// LineNumberTable:
// line 8: 0
// line 9: 2
// line 10: 6
// line 11: 10
// line 12: 17
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 18 0 args [Ljava/lang/String;
// 2 16 1 a I
// 6 12 2 b I
// 10 8 3 c I
}
運算指令
• 運算或算術指令用於對兩個操作數棧上的值進行某種特定的運算,並把結果存儲到操作數棧頂
類型轉換指令
• 類型轉換指令可以將兩種不同的數值類型進行相互轉換,這些轉換操作一般用於實現用戶代碼中的顯示類型轉換操作以
及用來處理字節碼指令集中數據類型相關指令無法與數據類型一一對應的問題
• 寬化類型處理和窄化類型處理
• L2b i2c i2s l2i
package jvm;
public class Demo01 {
public static void main(String[] args) {
int hour = 24;
long mi = hour* 60*60*1000;
long mic = hour * 60*60*1000*1000;
int i =0;
long l =1;
i = (int) l;
System.out.println(mic/mi);
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// stack=5, locals=9, args_size=1
// 0: bipush 24
// 2: istore_1
// 3: iload_1
// 4: bipush 60
// 6: imul
// 7: bipush 60
// 9: imul
// 10: sipush 1000
// 13: imul
// 14: i2l
// 15: lstore_2
// 16: iload_1
// 17: bipush 60
// 19: imul
// 20: bipush 60
// 22: imul
// 23: sipush 1000
// 26: imul
// 27: sipush 1000
// 30: imul
// 31: i2l
// 32: lstore 4
// 34: iconst_0
// 35: istore 6
// 37: lconst_1
// 38: lstore 7
// 40: lload 7
// 42: l2i
// 43: istore 6
// 45: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 48: lload 4
// 50: lload_2
// 51: ldiv
// 52: invokevirtual #3 // Method java/io/PrintStream.println:(J)V
// 55: return
// LineNumberTable:
// line 6: 0
// line 7: 3
// line 8: 16
// line 9: 34
// line 10: 37
// line 11: 40
// line 12: 45
// line 13: 55
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 56 0 args [Ljava/lang/String;
// 3 53 1 hour I
// 16 40 2 mi J
// 34 22 4 mic J
// 37 19 6 i I
// 40 16 7 l J
}
對象創建與訪問指令
• 創建類實例的指令:new
• 創建數組的指令:newarray anewarray multianewarray
• 訪問類字段:getfield putfield getstatic putstatic
• 把數組元素加載到操作數棧的指令:aload
• 將操作數棧的值存儲到數組元素:astore
• 取數組長度的指令:arraylength
• 檢查實例類型的指令:instanceof checkcast
package jvm;
public class Demo2 {
public static void main(String[] args) {
User user = new User();
user.name = "h1";
String name = user.name;
User[] users = new User[10];
int[] ints = new int[10];
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// stack=2, locals=5, args_size=1
// 0: new #2 // class jvm/User
// 3: dup
// 4: invokespecial #3 // Method jvm/User."<init>":()V 初始化方法
// 7: astore_1
// 8: aload_1
// 9: pop
// 10: ldc #4 // String h1 常量壓棧
// 12: putstatic #5 // Field jvm/User.name:Ljava/lang/String;
// 15: aload_1
// 16: pop
// 17: getstatic #5 // Field jvm/User.name:Ljava/lang/String;
// 20: astore_2
// 21: bipush 10
// 23: anewarray #2 // class jvm/User 引用類型的數組
// 26: astore_3
// 27: bipush 10
// 29: newarray int //基本類型的數組
// 31: astore 4
// 33: return
// LineNumberTable:
// line 6: 0
// line 7: 8
// line 8: 15
// line 10: 21
// line 11: 27
// line 12: 33
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 34 0 args [Ljava/lang/String;
// 8 26 1 user Ljvm/User;
// 21 13 2 name Ljava/lang/String;
// 27 7 3 users [Ljvm/User;
// 33 1 4 ints [I
}
操作數棧管理指令
• 操作數棧指令用於直接操作操作數棧
• 操作數棧的一個或兩個元素出棧:pop pop2
• 複製棧頂一個或兩個數值並將複製或雙份複製值重新壓入棧頂:dup dup2 dup_x1 dup_x2
• 將棧頂的兩個數值替換:swap
控制轉移指令
• 控制轉移指令可以讓java虛擬機有條件或無條件的從指定的位置指令而不是控制轉移指令的下一條指令繼續執行程序。可以認爲控制轉移指令就是在修改pc寄存器的值
• 條件分支:ifeq iflt ifle ifne ifgt ifnull ifcmple
• 複合條件分支:tableswitch lookupswitch
• 無條件分支:goto goto_w jsr jsr_w ret
方法調用指令
• Invokevirtual指令用於調用對象的實例方法,根據對象的實際類型進行分派
• Invokinterface指令用於調用接口方法,它會在運行時搜索一個實現了這個接口的對象,找出適合的方法進行調用
• Invokespecial指令用於調用一些需要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法
• Invokestatic指令用於調用類方法
package jvm;
public class Demo7 {
public static void main(String[] args) {
int count =10;
int i = 8;
if (++i>count) {
System.out.println("i>count");
}else {
System.out.println("i<count");
}
}
// public static void main(java.lang.String[]);
// descriptor: ([Ljava/lang/String;)V
// flags: ACC_PUBLIC, ACC_STATIC
// Code:
// stack=2, locals=3, args_size=1
// 0: bipush 10
// 2: istore_1
// 3: bipush 8
// 5: istore_2
// 6: iinc 2, 1
// 9: iload_2
// 10: iload_1
// 11: if_icmple 25
// 14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 17: ldc #3 // String i>count
// 19: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
// 22: goto 33
// 25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
// 28: ldc #5 // String i<count
// 30: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
// 33: return
// LineNumberTable:
// line 5: 0
// line 6: 3
// line 7: 6
// line 8: 14
// line 10: 25
// line 12: 33
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 34 0 args [Ljava/lang/String;
// 3 31 1 count I
// 6 28 2 i I
// StackMapTable: number_of_entries = 2
// frame_type = 253 /* append */
// offset_delta = 25
// locals = [ int, int ]
// frame_type = 7 /* same */
}
方法返回指令
• 方法返回指令是根據返回值的類型區分的,包括 ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn,另外還有一條return指令供聲明爲void的方法、實例初始化方法以及類和接口的類初始化方法使用。
異常處理指令
• 在Java程序中顯示拋出異常的操作(throw 語句)都由athrow指令來實現,除了用throw語句顯式拋出異常情況之外,Java
虛擬機規範還規定了許多運行時異常會在其他Java虛擬機指令檢測到異常狀況時自動拋出。例如,在整數運算中,當除數
爲零時,虛擬機會在idiv或ldiv指令中拋出ArithmeticException異常。而在Java虛擬機中,處理異常(catch語句)不是由字節
碼指令來實現的(很久之前曾經使用jsr和ret指令來實現,現在已經不用了),而是採用異常表來完成的。
同步指令
同步一段指令集序列通常是由Java語言中的synchronized語句塊來表示的,Java虛擬機的指令集中有monitorenter和monitorexit兩條指令來支持synchronized關鍵字的語義
package jvm;
public class Demo8 {
public static void main(String[] args) {
}
public synchronized void f1() {
}
public void f2() {
synchronized (this) {}
}
// Code:
// stack=2, locals=3, args_size=1
// 0: aload_0
// 1: dup
// 2: astore_1
// 3: monitorenter
// 4: aload_1
// 5: monitorexit
// 6: goto 14
// 9: astore_2
// 10: aload_1
// 11: monitorexit #鎖的都是當前對象 所有隻加鎖一次 可重入鎖
// 12: aload_2
// 13: athrow
// 14: return
// Exception table:
// from to target type
// 4 6 9 any
// 9 12 9 any
// LineNumberTable:
// line 14: 0
// line 15: 14
// LocalVariableTable:
// Start Length Slot Name Signature
// 0 15 0 this Ljvm/Demo8;
// StackMapTable: number_of_entries = 2
// frame_type = 255 /* full_frame */
// offset_delta = 9
// locals = [ class jvm/Demo8, class java/lang/Object ]
// stack = [ class java/lang/Throwable ]
// frame_type = 250 /* chop */
// offset_delta = 4
}
點關注不迷路: