java程序運行堆棧分析

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字節碼的時候是按照指定的字節碼指令進行操作
  • 線程棧中定義的局部變量數據將會壓入操作棧中進行計算然後存儲到本地變量表中,並且會將執行代碼的行數記錄到程序計數器中
  • 線程調用方法的時候會新創建一個新的棧幀執行對應的方法中的代碼,當方法執行完成之後棧幀將會從當前的線程中彈出
發佈了72 篇原創文章 · 獲贊 25 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章