在Java代碼中,類型的加載、鏈接與初始化過程都是在程序運行期間完成的java
理解:public static int number = 666;
程序員
上面這段代碼,在類加載的鏈接階段,爲對象number分配內存,並初始化爲0;而後再初始化階段在賦予正確的初始值:666數據庫
Java程序對類的使用方式可分爲兩種數組
全部的Java虛擬機實現必須在每一個類或接口被Java程序「首次主動使用」時才初始化他們網絡
代碼一數據結構
public class Test01 { public static void main(String[] args) { System.out.println(Child01.str); } } class Father01 { public static String str = "作一個好人!"; static { System.out.println("Father01 static block"); } } class Child01 extends Father01 { static { System.out.println("Child01 static block"); } }
運行結果作一個好人!dom
Father01 static block 作一個好人!
代碼二jvm
public class Test01 { public static void main(String[] args) { System.out.println(Child01.str2); } } class Father01 { public static String str = "作一個好人!"; static { System.out.println("Father01 static block"); } } class Child01 extends Father01 { public static String str2 = "作一個好人!"; static { System.out.println("Child01 static block"); } }
運行結果函數
Father01 static block Child01 static block 作一個好人!
分析:spa
以上驗證的是類的初始化狀況,那麼如何驗證類的加載狀況呢,能夠經過在啓動的時候配置虛擬機參數:-XX:+TraceClassLoading
查看
運行代碼一,查看輸出結果,能夠看見控制檯打印了very多的日誌,第一個加載的是java.lang.Object
類(無論加載哪一個類,他的父類必定是Object類),後面是加載的一系列jdk的類,他們都位於rt包下。往下查看,能夠看見Loaded classloader.Child01
,說明即便沒有初始化Child01,可是程序依然加載了Child01類。
[Opened /usr/local/jdk1.8/jre/lib/rt.jar] [Loaded java.lang.Object from /usr/local/jdk1.8/jre/lib/rt.jar] ... [Loaded java.lang.Void from /usr/local/jdk1.8/jre/lib/rt.jar] [Loaded classloader.Father01 from file:/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/] [Loaded classloader.Child01 from file:/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/] Father01 static block 作一個好人! [Loaded java.lang.Shutdown from /usr/local/jdk1.8/jre/lib/rt.jar] [Loaded java.lang.Shutdown$Lock from /usr/local/jdk1.8/jre/lib/rt.jar]
由於前一章節使用了JVM參數,因此對其作一下簡單的介紹
-XX:
開頭的-XX:+<option>
,表示開啓option選項-XX:-<option>
,表示關閉option選項-XX:<option>=<value>
,表示將option選項的值設置爲valuepublic class Test02 { public static void main(String[] args) { System.out.println(Father02.str); } } class Father02{ public static final String str = "作一個好人!"; static { System.out.println("Father02 static block"); } }
執行結果
作一個好人!
分析
能夠看見,此段代碼並無初始化Father02類。這是由於final表示的是一個常量,在編譯階段常量就會被存入到調用這個常量的方法所在的類的常量池當中,本質上,調用類並無直接引用到定義常量的類,所以並不會觸發定義常量的類的初始化。在本代碼中,常量str會被存入到Test02的常量池中,以後Test02與Father02沒有任何關係,甚至能夠刪除Father02的class文件。
咱們反編譯一下Test02類
Compiled from "Test02.java" public class classloader.Test02 { public classloader.Test02(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #4 // String 作一個好人! 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
第一塊是Test02類的構造方法,第二塊是咱們要看的main方法。能夠看見3: ldc #4 // String 作一個好人!
,此時這個值已是肯定無疑的作一個好人!
了,而不是Father02.str
,證明了上面說的在編譯階段常量就會被存入到調用這個常量的方法所在的類的常量池當中。
因前一章節涉及到了助記符,因此介紹下本章節涉及到的助記符及擴展
1
推送至棧頂(這類助記符只有iconst_m1 - iconst_5七個)public class Test03 { public static void main(String[] args) { System.out.println(Father03.str); } } class Father03 { public static final String str = UUID.randomUUID().toString(); static { System.out.println("Father03 static block"); } }
運行結果
Father03 static block a60c5db4-2673-4ffc-a9f0-2dbe53fae583
分析
本代碼與示例二的區別在於str
的值是在運行時確認的,而不是編譯時就肯定好的,屬於運行期常量,而不是編譯期常量。當一個常量的值並不是編譯期間肯定的,那麼其值就不會被放到調用類的常量池中,這時在程序運行時,會致使主動使用這個常量所在的類,致使這個類被初始化。
代碼一
public class Test04 { public static void main(String[] args) { Father04 father04_1 = new Father04(); System.out.println("-----------"); Father04 father04_2 = new Father04(); } } class Father04 { static { System.out.println("Father04 static block"); } }
運行結果
Father04 static block -----------
分析
代碼二
public class Test04 { public static void main(String[] args) { Father04[] father04s = new Father04[1]; System.out.println(father04s.getClass()); } }
運行結果
class [Lclassloader.Father04;
分析
[Lclassloader.Father04
,這是虛擬機在運行期生成的。 -> 對於數組示例來講,其類型是有JVM在運行期動態生成的,表示爲[Lclassloader.Father04
這種形式,動態生成的類型,其父類就是Object。反編譯一下:
public static void main(java.lang.String[]); Code: 0: iconst_1 1: anewarray #2 // class classloader/Father04 4: astore_1 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: aload_1 9: invokevirtual #4 // Method java/lang/Object.getClass:()Ljava/lang/Class; 12: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 15: return
代碼一
public class Test05 { public static void main(String[] args) { System.out.println(Child05.j); } } interface Father05 { int i = 5; } interface Child05 extends Father05 { int j = 6; }
運行結果
6
分析
代碼二
public class Test05 { public static void main(String[] args) { System.out.println(Child05.j); } } interface Father05 { int i = 5; } interface Child05 extends Father05 { int j = new Random().nextInt(8); }
運行結果
6
將Father05.class文件刪除,運行結果
Exception in thread "main" java.lang.NoClassDefFoundError: classloader/Father05 at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at classloader.Test05.main(Test05.java:15) Caused by: java.lang.ClassNotFoundException: classloader.Father05 at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 13 more
分析
代碼三
public class Test05 { public static void main(String[] args) { System.out.println(Child05.j); } } interface Father05 { Thread thread = new Thread() { { System.out.println("Father05 code block"); } }; } class Child05 implements Father05 { public static int j = 8; }
運行結果
8
分析
代碼四
public class Test05 { public static void main(String[] args) { System.out.println(Father05.thread); } } interface GrandFather { Thread thread = new Thread() { { System.out.println("GrandFather code block"); } }; } interface Father05 extends GrandFather{ Thread thread = new Thread() { { System.out.println("Father05 code block"); } }; }
運行結果
Father05 code block Thread[Thread-0,5,main]
分析
代碼一
public class Test06 { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); System.out.println("i:" + Singleton.i); System.out.println("j:" + Singleton.j); } } class Singleton { public static int i; public static int j = 0; private static Singleton singleton = new Singleton(); private Singleton() { i ++; j ++; } public static Singleton getInstance() { return singleton; } }
運行結果
i:1 j:1
分析
首先Singleton.getInstance();
進入Singleton
的getInstance
方法,getInstance
會返回Singleton
的實例,Singleton
的實例是new Singleton();
出來的,所以調用了自定義的私有構造方法。在調用構造方法以前,給靜態變量賦值,i
默認賦值爲0,j
顯式的賦值爲0,通過構造函數以後,值都爲1。
代碼二
public class Test06 { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); System.out.println("i:" + Singleton.i); System.out.println("j:" + Singleton.j); } } class Singleton { public static int i; private static Singleton singleton = new Singleton(); private Singleton() { i ++; j ++; } public static int j = 0; public static Singleton getInstance() { return singleton; } }
運行結果
i:1 j:0
分析
程序主動使用了Singleton類,準備階段對類的靜態變量分配內存,賦予默認值,下面給出類在鏈接及初始化階段常量的值的變化
故返回的值i爲1,j爲0
類被加載後,就進入鏈接階段。鏈接就是將已經讀入到內存中的類的二進制數據合併到虛擬機的運行時環境中去
類的驗證的內容
在準備階段,Java虛擬機爲類的靜態變量分配內存,並設置默認的初始值。例如對於下面的Sample類,在準備階段,將爲int類型的靜態變量
i
分配4個字節的內存空間,而且賦默認值0;爲long類型的靜態變量j分配8個字節的內存空間,並賦予默認值0
public class Sample { private static int i = 8; private static long j = 8L; ...... }
在初始化階段,Java虛擬機執行類的初始化語句,爲類的靜態變量賦予初始值。在程序中,靜態變量的初始化有兩種途徑:
- 在靜態變量的聲明處初始化
- 在靜態代碼塊中初始化
靜態變量的聲明語句,預計靜態代碼塊都被看做類的初始化語句,Java虛擬機會按照初始化語句在類文件中的前後順序來依次執行他們
當Java虛擬機初始化一個類時,要求他的全部父類都已經被初始化,可是這條規則並不適用於接口
所以,一個父接口並不會由於他的子接口或實現類的初始化而初始化,只有當程序首次使用特定接口的靜態變量時,纔會致使該接口的初始化。代碼參照代碼理解-接口的初始化
調用ClassLoader類的loadClass方法加載一個類,並非對類的主動使用,不會致使類的初始化
類的生命週期除了前文提到的加載、鏈接、初始化以外,還有類示例化,垃圾回收和對象終結
<init>
。針對源代碼中每個類的構造方法,Java編譯器都產生一個<init>
方法