Java程式運行原理分析

  • 2019 年 10 月 7 日
  • 筆記

class文件內容

  • class文件包含Java程式執行的位元組碼
  • 數據嚴格按照格式緊湊排列在class文件的二進位流,中間無分割符
  • 文件開頭有一個0xcafebabe(16進位)特殊的標誌

JVM運行時數據區

執行緒獨佔: 每個執行緒都會有它獨立的空間,隨執行緒的生命周期而創建和銷毀 執行緒共享: 所有執行緒都能訪問這塊記憶體數據,隨虛擬機或GC而創建和銷毀 方法區

  • 方法區是各個執行緒共享的記憶體區域
  • 用於存儲已被虛擬機載入的類資訊, 常量,靜態變數, 即時編譯後的程式碼等數據
  • 雖然Java虛擬機規範把方法區描述為堆的一個邏輯部分, 但它卻有一個別名叫Non-Heap, 目的應該是與Java堆區分開來
  • Oracle的Hotspot虛擬機在Java7中方法區放在』永久代』(Permanent Generation), Java8放在元數據空間, 並且通過GC機制對這個區域進行管理
  • 運行時常量池是方法區的一部分 Java堆
  • Java堆是被所有共享的一塊記憶體區域, 在虛擬機啟動時創建
  • 存放對象的實例
  • 垃圾收集器的主要管理區域
  • Java堆還可以細分為: 新生代和老年代, 新生代又可以細分為Eden 空間, From Survivor空間 和To Survivor空間
  • 空間滿了會拋OutOfMemoryError

Java虛擬機棧

  • Java虛擬機棧是執行緒私有的, 它的生命周期與執行緒相同
  • Java虛擬機棧描述的是Java方法執行的記憶體模型: 每個方法被執行的的時候都會同時創建一個棧幀(棧幀是方法運行時的基礎數據結構)用於存儲局部變數表, 操作棧, 動態鏈接, 方法出口等資訊.
  • 棧記憶體默認最大是1M, 超出則拋出StackOverFlowError

本地方法棧

  • 本地方法棧與虛擬機棧的功能類似, 虛擬機棧是為虛擬機執行Java方法而準備的, 本地方法棧是為虛擬機使用Native本地方法而準備的
  • Hotspot虛擬機中虛擬機棧與本地方法棧的實現方式一樣, 超出大小後也會拋StackOverFlowError

程式計數器

  • 程式計數器是執行緒私有的一塊較小的記憶體空間
  • 記錄當前執行緒執行的位元組碼位置, 存儲的是位元組碼指令地址, 如果執行Native方法, 則計數器為空
  • CPU同一時間, 只會執行一條執行緒的指令. JVM多執行緒會輪流切換並分配CPU的執行時間的方式. 為了執行緒切換後, 需要通過程式計數器來恢復正確的執行位置 查看class文件內容 使用Demo.Java進行測試, 運行javac Demo.java編譯成class文件, 然後運行javap -v Demo.class > Demo.txt查看class文件內容 Demo.Java public class Demo{ public static void main(String[] args){ int x = 500; int y = 100; int a = x / y; int b = 50; System.out.println(a + b); } } Demo.txt Classfile /E:/*/Demo.class Last modified 2019-6-30; size 412 bytes MD5 checksum efd785af33e58aa9fc9834110b74b87b Compiled from "Demo.java" public class Demo minor version: 0 //次版本號 major version: 52 //主版本號 版本號規則: JDK5,6,7,8分別對應49,50,51,52 flags: ACC_PUBLIC, ACC_SUPER //訪問標誌Constant pool: // 常量池 類資訊包含的靜態常量, 編譯之後就能確認 #1 = Methodref #5.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V #4 = Class #19 // Demo #5 = Class #20 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 main #11 = Utf8 ([Ljava/lang/String;)V #12 = Utf8 SourceFile #13 = Utf8 Demo.java #14 = NameAndType #6:#7 // "<init>":()V #15 = Class #21 // java/lang/System #16 = NameAndType #22:#23 // out:Ljava/io/PrintStream; #17 = Class #24 // java/io/PrintStream #18 = NameAndType #25:#26 // println:(I)V #19 = Utf8 Demo #20 = Utf8 java/lang/Object #21 = Utf8 java/lang/System #22 = Utf8 out #23 = Utf8 Ljava/io/PrintStream; #24 = Utf8 java/io/PrintStream #25 = Utf8 println #26 = Utf8 (I)V { public Demo(); // 默認隱式無參的構造函數 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 1: 0 public static void main(java.lang.String[]); //程式的入口main方法 descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC //訪問控制 Code: stack=3, locals=5, args_size=1 //方法棧棧幀中操作數棧的深度,本地變數數量,參數數量 0: sipush 500 //Jvm執行引擎執行這些源碼編譯過後的指令碼, javap翻譯出來的 3: istore_1 //是操作符, class 文件記憶體儲的是指令碼, 前面的數據是偏移量, 4: bipush 100 //Jvm根據這個去區分不同的指令. 詳情參照'JVM指令碼錶' 6: istore_2 7: iload_1 8: iload_2 9: idiv 10: istore_3 11: bipush 50 13: istore 4 15: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 18: iload_3 19: iload 4 21: iadd 22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 25: return LineNumberTable: line 3: 0 line 4: 4 line 5: 7 line 6: 11 line 7: 15 line 8: 25 } SourceFile: "Demo.java" 程式完整運行分析