1.前言 相信許多開發同學看過《深入理解java虛擬機》,也閱讀過java虛擬機規範,書籍和文檔給人的感覺不夠直觀,本文從一個簡單的例子來看看jvm是如何工作的吧。 本文所有操作均在mac上進行。 2.示例代碼 示例代碼採用最常見的雙重檢索單例模式: package interview.desgin ...
1.前言
相信許多開發同學看過《深入理解java虛擬機》,也閱讀過java虛擬機規範,書籍和文檔給人的感覺不夠直觀,本文從一個簡單的例子來看看jvm是如何工作的吧。
本文所有操作均在mac上進行。
2.示例代碼
示例代碼採用最常見的雙重檢索單例模式:
package interview.desginpattern.singletion.doublecheck;
import java.io.Serializable;
public class Singleton implements Serializable {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
3.指令
經過編譯後,我們得到class文件,然後用javap命令查看相關指令
javap -c Singleton:
public class interview.desginpattern.singletion.doublecheck.Singleton implements java.io.Serializable {
public static interview.desginpattern.singletion.doublecheck.Singleton getInstance();
Code:
0: getstatic #2 // get static instance
3: ifnonnull 37 // instance is null,jump 37
6: ldc #3 // push class Singleton to operand stack
8: dup // Duplicate the top (class Singleton) operand stack value
9: astore_0 // Store reference(class Singleton) into local variable
10: monitorenter // synchronized start
11: getstatic #2
14: ifnonnull 27 // instance is null,jump 27
17: new #3 // new class interview/desginpattern/singletion/doublecheck/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2
27: aload_0 // Load reference (class Singleton) from local variable
28: monitorexit // synchronized end
29: goto 37
32: astore_1
33: aload_0
34: monitorexit // synchronized end (exception)
35: aload_1
36: athrow // throw exception
37: getstatic #2
40: areturn
Exception table:
from to target type
11 29 32 any
32 35 32 any
static {}; // static code
Code:
0: aconst_null // Push the null object reference onto the operand stack
1: putstatic #2 // set static instance
4: return
}
有以下幾點需要說明:
- operand stack 即操作數棧,區別於jvm虛擬機棧,是屬於棧幀(frame)中的數據結構
- local variable 即本地變數,也屬於棧幀中的結構
- synchronized 被解析成monitorenter 和 monitorexit兩條指令,並且需要處理異常情形
我們知道一般來講jvm 運行時數據包括,pc寄存器,stack(虛擬機棧),heap,method area, 運行時常量池,本地方法棧。stack 中的每一幀包括,operand stack, local variable , 和指向常量池的指針。
4.class文件
上一節中只是展示了java代碼編譯成class文件,包含了哪些指令,但是class文件包含的信息遠遠不止這些。
我們在IDEA 中使用插件 jclasslib bytecode viewer 查看 class文件具體包含哪些信息:
4.1.一般信息
這裡省略了class 文件的魔法數標誌,0xCAFEBABE
4.2.介面和欄位
4.3.方法
- <init> 方法對應Singletion的構造方法
- <clinit> 方法對應Singletion.class的構造方法
我們還可以查看getInstatnce編譯後的指令 (chapter 3):
4.4.常量池
上述數據結構(4.1, 4.2, 4.3)不會存字元串字面量,而是指向常量池的引用:
4.5.屬性
5.載入
載入即根據class文件創建對象/介面.
有兩種載入器,分別是bootstrap class loader,和user-defined class cloader,我們可以自定義載入器,比如從網路,或者從加密的class文件中載入對象。
任何一個class/inteface 被限定名 + 類載入器唯一確定,所以jvm實現者在併發的情況下需要確保此唯一性約束。
一般而言,載入流程如下
- 看該文件有沒有被對應的載入器進行載入
- 驗證該文件是不是class文件,major or minor version 是否被支持
- 檢查父類是否被載入
- 檢查介面是否被載入
6.鏈接(驗證,準備,解析)
驗證:確保class文件結構是否正確,是否打破jvm規範
準備:給class或者interface的靜態屬性設置預設值(區別於顯式賦值)
解析:給 symbolic references 賦予確定的值(除了 invokedynamic,其他都可以唯一確定)
7.初始化
初始化:調用class或者interface的<init>, <cinit> 方法 (4.3)
8.總結
本文概述了java代碼是如何載入到jvm中的,jvm有各種不同的實現(我們最熟悉的hotspot虛擬機),其中細節可能不盡相同。載入到jvm中並不代表程式生命周期的結束,運行時的情況也值得關註。
9.參考
https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf