1. 程式人生 > >JVM 類載入機制:編譯器常量與初始化

JVM 類載入機制:編譯器常量與初始化

1. 前言

最近在研究JVM虛擬機器類載入機制的時候,我們瞭解到了類載入機制的生命週期以及在準備階段,JVM虛擬機器會對類的靜態變數進行初始化,這個時候只是會將靜態變數初始化為預設的初始值。對靜態變數的定義的初始化值,將會被封裝到clinit()方法中,直到初始化階段進行初始化。但是對於靜態常量會使一個特例,下面我們將來看看JVM虛擬機器對於靜態常量是如何進行操作的。

2.類載入機制

下面我們再來複習一下JVM虛擬機器類載入機制:

  • 載入:這是由類載入器執行的,該步驟將查詢位元組碼(通常在classpath路徑查詢,但不是必須的),並從這些位元組碼中建立一個Class物件;
  • 連結:在連結階段驗證類的位元組碼,為靜態域分配儲存空間,並且如果必要的話,會解析這個類建立的對其它類的所有引用;
  • 初始化:如果該類具有超類,則對其進行初始化,執行靜態初始化器和靜態初始化塊。

初始化階段被延遲到了對靜態方法或者非常數靜態域進行首次引用時才執行:

下面我們來看一下以下程式碼:

import java.util.Random;
class Initable{
 static final int staticFinal = 45;
 static final int staticFinal2 = ClassInitialization.rand.
nextInt(1000); static{ System.out.println("Initializing Initable"); System.out.println("Initable---------------"); } } class Initable2{ static int staticNonFinal = 145; static{ System.out.println("Initializing Initable2"); System.out.println("Initable2---------------"); } } class Initable3
{ static int staticNonFinal = 54; static{ System.out.println("Initializing Initable3"); System.out.println("Initable3---------------"); } } public class ClassInitialization { public static Random rand = new Random(45); public static void main(String[] args) throws ClassNotFoundException { Class initable = Initable.class; System.out.println("After Creating Initable Ref"); System.out.println(Initable.staticFinal); System.out.println("------------------"); System.out.println(Initable.staticFinal2); System.out.println(Initable2.staticNonFinal); Class initable3 = Class.forName("com.chenxyt.java.practice.Initable3"); System.out.println("After Creating Initable3 Ref"); System.out.println(Initable3.staticNonFinal); } } 執行結果: After creating Initable ref 47 Initializing Initable 258 Initializing Initable2 147 Initializing Initable3 After creating Initable3 ref 74

3. 結果分析

初始化有效的實現了儘可能的“惰性”。從結果中可以看出.class並不會引發初始化,相反使用Class.forName()時則立刻完成了初始化的功能。靜態常量與初始化過程總結以下規則:

  1. 如果一個static final是“編譯器常量”,就像Initable.staticFinal那樣,那麼這個值不需要對Initable進行初始化就可以被讀取。因為它在準備階段變數的初始值就會被直接初始化,具體原因是由於擁有final欄位的靜態常量在它的欄位屬性表中會出現ConstantValue屬性。如果只是將一個域設定為static和final時,還不足以確保這種行為。並且一個static final不能確定在編譯期間就能確定它的值,則它還不能確保這種行為。比如static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
  2. 如果一個static域不是final的,那麼對它進行訪問時,總是要求在它被讀取之前,要先進行連結(為這個域分配儲存空間)和初始化(初始化該儲存空間),就像在Initable2.staticNonFinal的訪問中看到的那樣。