類初始化基本知識java
當使用 java 命令運行 Java 程序時,會啓動一個 Java 虛擬機進程。同一個 JVM 的全部線程、全部變量都處於同一個進程裏,他們都使用該 JVM 進程的內存區。當系統出現以下狀況時,JVM 進程將被終止。面試
System.exit()
或 Runtime.getRuntime().exit()
兩個運行的 Java 程序處於兩個不一樣的 JVM 進程中,兩個 JVM 之間並不會共享數據。緩存
當程序主動使用某個類時,若是該類還未被加載到內存中,則系統會經過加載、鏈接、初始化三個步驟來進行該類的初始化。這三個步驟統稱爲「類加載」或「類初始化」。網絡
「類加載」指的是將類的 class 文件讀入內存,併爲之建立一個 java.lang.Class
對象。換言之,程序中使用任何類時,系統都會爲之創建一個 java.lang.Class
對象。jvm
系統中全部的類實際上也是實例,它們都是
java.lang.Class
的實例。測試
類的加載由類加載器完成,類加載器由 JVM 提供。除此以外,開發者能夠經過集成 ClassLoader 基類來自定義類加載。**類加載器一般無須等到」首次使用「該類時才加載它,Java 虛擬機規範容許系統預先加載某些類。this
類被加載後,系統會爲之生成對應的 Class 對象,接着就會進入鏈接階段。鏈接階段負責把類的二進制數據合併到 JRE 中。類接連分爲以下三個階段:線程
類初始化階段主要就是虛擬機堆類變量進行初始化。在 Java 類中堆類變量指定初始值有兩種方法:code
若是類變量沒有指定初始值,則採用默認初始值對象
{% note success no-icon %}
{% endnote%}
JVM 初始化一個類包含以下幾個步驟:
第 2 個步驟中,若是直接父類又有父類,會再次重複這三個步驟
實例初始化塊負責對象執行初始化,而類初始化塊是類相關的,系統在類初始化階段執行,而不是在建立對象時才執行。所以,類初始化塊老是比實例初始化塊先執行。只有當類初始化完成以後,才能夠在系統中使用這個類,包括訪問類的類方法、類變量或者用這個類來建立實例。
栗子
Root.java:
public class Root { static { int root = 1; System.out.println("Root 的類初始化塊"); } { System.out.println("Root 的實例初始化塊"); } public Root() { System.out.println("Root 的無參構造器"); } }
Mid.java:
public class Mid extends Root { static { int mid = 2; System.out.println("Mid 的類初始化塊"); } { System.out.println("Mid 的實例初始化塊"); } public Mid() { System.out.println("Mid 的無參構造器"); } public Mid(String msg) { this(); System.out.println("Mid 的有參構造器,其參數值:" + msg); } }
Leaf.java:
public class Leaf extends Mid { static { int leaf = 3; System.out.println("Leaf 的類初始化塊"); } { System.out.println("Leaf 的實例初始化塊"); } public Leaf() { super("初始化測試"); System.out.println("執行Leaf的構造器"); } }
Test.java:
public class Test { public static void main(String[] args) { new Leaf(); new Leaf(); } }
運行結果會是怎樣的呢?停下來想想。
輸出結果:
Root 的類初始化塊 Mid 的類初始化塊 Leaf 的類初始化塊 Root 的實例初始化塊 Root 的無參構造器 Mid 的實例初始化塊 Mid 的有參構造器,其參數值:初始化測試 Leaf 的實例初始化塊 執行Leaf的構造器 Root 的實例初始化塊 Root 的無參構造器 Mid 的實例初始化塊 Mid 的有參構造器,其參數值:初始化測試 Leaf 的實例初始化塊 執行Leaf的構造器
說明:
實例初始化塊就是指沒有 static 修飾的初始化塊。當建立該類的 Java 對象時,系統老是先調用該類定義的實例初始化塊(固然,類初始化要已經先完成)。實例初始化是在建立 Java 對象時隱式執行的,並且,在構造器執行以前自動執行。
實例初始化栗子
public class InstanceTest { { a = 1; } int a = 2; public static void main(String[] args) { // 輸出 2 System.out.println(new InstanceTest().a); } }
若是上面例子,將實例初始化塊和實例變量聲明順序調換,輸出就會變爲 1。
建立 Java 對象時,系統先爲該對象的全部實例變量分配內存(前提是該類已被加載過),接着程序對這些實例變量進行初始化:先執行實例初始化塊或聲明實例變量時指定的初始值(按照它們在源碼中的前後順序賦值),而後再執行構造器裏指定的初始值。
{% note success no-icon %}
實際上實例初始化塊是一個假象,使用 javac
命令編譯 Java 類後,該 Java 類中的實例初始化塊會消失—實例初始化塊中代碼會被「還原」到每一個構造器中,且位於構造器全部代碼的前面。
{% endnote%}
Java 程序首次經過下面 6 種方式使用某個類或接口時,系統就會初始化該類或接口:
java.lang.Class
對象。例如 Class.forName("Person")
java.exe
命令運行某個主類{% note warning no-icon %}
對於 final
型的類變量,若是該類變量的值在編譯時就肯定了,那麼,這個類變量至關於「宏變量」。Java 編譯器會在編譯時直接將該類變量出現的地方替換爲它實際的值。所以,程序使用這種靜態變量不會致使該類的初始化。
{% endnote %}
栗子:
class MyTest { static { System.out.println("靜態初始化塊"); } static final String compileConstant = "類初始化 demo"; } public class ComileConstantTest { public static void main(String[] args) { System.out.println(MyTest.compileConstant); } }
輸出:
類初始化 demo
因而可知,的確沒有初始化 MyTest 類。
當類變量使用了
final
修飾,而且,它的值在編譯時就能肯定,那麼它的值在編譯時就肯定了,程序中使用它的地方至關於使用了常量。
若是上面栗子中代碼改成以下:
static final String compileConstant = System.currentTimeMillis() + "";
這時候輸出就是:
靜態初始化塊 1596804413248
由於上面 compileConstant
修改以後,它的值必須在運行時才能肯定,所以,觸發了 MyTestg 類的初始化。
此外,ClassLoader
類的 loadClass()
方法來加載某個類時,該方法只是加載類,並不會執行類的初始化。使用 Class.forName()
靜態方法再回強制初始化類。
栗子:
package class_load; /** * description: * * @author Michael * @date 2020/8/7 * @time 8:54 下午 */ class Tester { static { System.out.println("Tester 類的靜態初始化塊"); } } public class ClassLoadTest { public static void main(String[] args) throws ClassNotFoundException { ClassLoader cl = ClassLoader.getSystemClassLoader(); cl.loadClass("class_load.Tester"); System.out.println("系統加載 Tester 類"); Class.forName("class_load.Tester"); } }
輸出:
系統加載 Tester 類 Tester 類的靜態初始化塊
經測試能夠發現,loadClass
方法確實沒有觸發類的初始化,而 Class.forName
則會初始化 Tester
類。
類加載器負責將 .class
文件(可能在磁盤上,也可能在網絡上)加載到內存中,併爲之生成 java.lang.Class
對象。
類加載器負責加載全部的類,系統爲全部被載入內存中的類生成一個 java.lang.Class
對象/實例。一旦一個類被載入 JVM 中,同一個類就不會再次被載入。正是由於有這樣的緩存機制存在,因此 Class 修改以後,必須重啓 JVM 修改纔會生效。
類加載器加載 Class
大體通過以下步驟:
開發者也能夠經過繼承
ClassLoader
來自定義類加載器。由於暫時未涉及這塊,本文暫且略過。
本文重點是瞭解了類初始化的流程,同時,也結合栗子比較了與實例初始化的區別。類初始化塊、實例初始化塊、構造器的執行順序也是面試題常考的內容。最後補充了類加載機制的內容,暫時僅是瞭解。
繪圖採用的 ProcessOn 在線繪製,安利~
生命不息,折騰不止!關注 「Coder 魔法院」,祝你 Niubilitiy !🐂🍺