1. java程序源代碼與字節碼
源代碼
public class StackHeapAnalysis {
// java 運行堆棧分析
public static void main(String[] args) {
//define my wallet totel balance
int balance = 500;
// birthday cake
int cakeVal = 99;
int cakeNum = 1;
// birthday flower
int flower = 2;
int flowerNum = 99;
int cakeSpent = cakeNum * cakeVal;
int flowerSpent = flower * flowerNum;
int totalSpent = flowerSpent + cakeSpent;
int currentBalance = balance - totalSpent;
System.out.printf("in order to celebrate birthday for my gift friends, i have spent %d\n", totalSpent);
System.out.printf("now my wallet balance is %d \n", currentBalance);
}
字節碼文件
public class com.xiaokunliu.homework.thread.base.StackHeapAnalysis
minor version: 0 // 最低版本號
major version: 52 // 主版本號 52 = 0x34 意味着是jdk8版本
flags: ACC_PUBLIC, ACC_SUPER // 訪問標識符
Constant pool: // 常量池
#1 = Methodref #4.#32 // java/lang/Object."<init>":()V
#2 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #35 // in order to celebrate birthday for my gift friends, i have spent %d\n
#4 = Class #36 // java/lang/Object
#5 = Methodref #37.#38 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#6 = Methodref #39.#40 // java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
#7 = String #41 // now my wallet balance is %d \n
#8 = Class #42 // com/xiaokunliu/homework/thread/base/StackHeapAnalysis
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/xiaokunliu/homework/thread/base/StackHeapAnalysis;
#16 = Utf8 main
#17 = Utf8 ([Ljava/lang/String;)V
#18 = Utf8 args
// 常量池省略....
{
public com.xiaokunliu.homework.thread.base.StackHeapAnalysis(); // 類的構造器
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/xiaokunliu/homework/thread/base/StackHeapAnalysis;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=6, locals=10, args_size=1 // 以下是main線程的字節碼操作指令
0: sipush 500
3: istore_1
4: bipush 99
6: istore_2
7: iconst_1
8: istore_3
9: iconst_2
10: istore 4
12: bipush 99
14: istore 5
16: iload_3
17: iload_2
18: imul
19: istore 6
21: iload 4
23: iload 5
25: imul
26: istore 7
28: iload 7
30: iload 6
32: iadd
33: istore 8
35: iload_1
36: iload 8
38: isub
39: istore 9
41: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
44: ldc #3 // String in order to celebrate birthday for my gift friends, i have spent %d\n
46: iconst_1
47: anewarray #4 // class java/lang/Object
50: dup
51: iconst_0
52: iload 8
54: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
57: aastore
58: invokevirtual #6 // Method java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
61: pop
62: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
65: ldc #7 // String now my wallet balance is %d \n
67: iconst_1
68: anewarray #4 // class java/lang/Object
71: dup
72: iconst_0
73: iload 9
75: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
78: aastore
79: invokevirtual #6 // Method java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
82: pop
83: return
LineNumberTable: // 字節碼存儲代碼在代碼表的數組中,LineNumberTable通過定位源文件的代碼行數與代碼表數據長度對應起來
line 15: 0
// ......
LocalVariableTable: // 線程本地局部變量表,存儲main線程中的局部變量信息
Start Length Slot Name Signature
0 84 0 args [Ljava/lang/String;
// ....
}
2. java運行堆棧分析
類執行分析準備
- 定義的class文件編譯爲二進制.class的時候,產生幻數標識,訪問標識
- 執行main的時候先執行當前類的init構造器完成初始化
- 線程執行包含程序計數器以及虛擬機棧(多個棧幀[操作數棧 + 局部變量表 + 動態鏈接 + 方法返回])
JVM的int入棧操作指令參考
## 參考鏈接
https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
## int入棧指令
iconst_<i> 操作常量 -1 - 5
ipush 操作一個字節的int數據
sipush 操作兩個字節的int數據
ldc 超過兩個字節的int數據
main方法執行分析
int balance = 500
分析
// source.java
int balance = 500;
// 字節碼文件
0: sipush 500
3: istore_1
// 其他定義變量的操作與上述一致
將500壓入操作數棧中並存儲在本地變量表,然後彈出操作數棧,同時程序計數器記錄當前代碼執行的位置
int cakeNum = 1;
與int flower = 2;
分析
// source.java
int cakeNum = 1;
int flower = 2;
// 字節碼文件
7: iconst_1
8: istore_3
9: iconst_2
10: istore 4
JVM將常量1和2壓入操作數棧並存儲在本地變量表中,然後彈出操作數棧,更新程序計數器當前執行的代碼的位置
- 進行乘法運算
// source.java
int cakeSpent = cakeNum * cakeVal;
// 字節碼
16: iload_3 // 1
17: iload_2 // 99
18: imul
19: istore 6
將本地變量表中的數據1 和 數據99分別壓入操作數棧中然後進行乘法運算最後將運算結果存儲在本地變量表中
其他的數學基本運算也是依次類推
- 打印輸出
//source.java
System.out.printf("in order to celebrate birthday for my gift friends, i have spent %d\n", totalSpent)
// 字節碼文件
41: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
44: ldc #3 // String in order to celebrate birthday for my gift friends, i have spent %d\n
46: iconst_1
47: anewarray #4 // class java/lang/Object
50: dup
51: iconst_0
52: iload 8
54: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
57: aastore
58: invokevirtual #6 // Method java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream;
61: pop
- 分析:
- getstatic #2 表示從常量池獲取靜態資源#2並將其加載到操作數棧中,同時創建新的棧幀,因爲屬於一個新的方法調用
- ldc 將常量池中將字符串數據壓入操作數棧中
- anewarray #4 從常量池中獲取靜態資源#4並將其創建一個新的對象數組引用
- dup 上述的對象引用複製到操作數棧的頂部
- iload 8 從本地變量表中加載totalSpent的數據
- invokestatic 執行整數轉換爲字符串的方法,將totalSpent轉換爲字符串
- aastore 將上述計算得到的結果存儲到本地變量表中
- invokevirtual 調用方法輸出字符串到控制檯中
- main的線程中彈出getstatic的棧幀
3. 小結
- java程序代碼轉換爲.class字節碼的時候是按照指定的字節碼指令進行操作
- 線程棧中定義的局部變量數據將會壓入操作棧中進行計算然後存儲到本地變量表中,並且會將執行代碼的行數記錄到程序計數器中
- 線程調用方法的時候會新創建一個新的棧幀執行對應的方法中的代碼,當方法執行完成之後棧幀將會從當前的線程中彈出