從一道面試題來認識java類載入時機與過程
1 開門見山
以前曾經看到過一個java的面試題,當時覺得此題很簡單,可是自己把程式碼執行起來,可是結果並不是自己想象的那樣。題目如下:
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
錯誤答案
count1=1
count2=1
正確答案
count1=1
count2=0
為神馬?為神馬?這要從java的類載入時機說起。
2 類的載入時機
3 何時開始類的初始化
- 建立類的例項
- 訪問類的靜態變數(除常量【被final修辭的靜態變數】原因:常量一種特殊的變數,因為編譯器把他們當作值(value)而不是域(field)來對待。如果你的程式碼中用到了常變數(constant variable),編譯器並不會生成位元組碼來從物件中載入域的值,而是直接把這個值插入到位元組碼中。這是一種很有用的優化,但是如果你需要改變final域的值那麼每一塊用到那個域的程式碼都需要重新編譯。
- 訪問類的靜態方法
- 反射如(Class.forName("my.xyz.Test"))
- 當初始化一個類時,發現其父類還未初始化,則先出發父類的初始化
- 虛擬機器啟動時,定義了main()方法的那個類先初始化
4 被動引用例子
- 子類呼叫父類的靜態變數,子類不會被初始化。只有父類被初始化。。對於靜態欄位,只有直接定義這個欄位的類才會被初始化.
- 通過陣列定義來引用類,不會觸發類的初始化
- 訪問類的常量,不會初始化類
class SuperClass {
static {
System.out.println("superclass init");
}
public static int value = 123;
}
class SubClass extends SuperClass {
static {
System.out.println("subclass init");
}
}
public class Test {
public static void main(String[] args) {
System.out.println(SubClass.value);// 被動應用1
SubClass[] sca = new SubClass[10];// 被動引用2
}
}
程式執行輸出 superclass init class ConstClass {
static {
System.out.println("ConstClass init");
}
public static final String HELLOWORLD = "hello world";
}
public class Test {
public static void main(String[] args) {
System.out.println(ConstClass.HELLOWORLD);// 呼叫類常量
}
}
程式輸出結果5 類的載入過程
5.1 載入
“載入”(Loading)階段是“類載入”(Class Loading)過程的第一個階段,在此階段,虛擬機器需要完成以下三件事情:
1、 通過一個類的全限定名來獲取定義此類的二進位制位元組流。
2、 將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。
3、 在Java堆中生成一個代表這個類的java.lang.Class物件,作為方法區這些資料的訪問入口。
載入階段即可以使用系統提供的類載入器在完成,也可以由使用者自定義的類載入器來完成。載入階段與連線階段的部分內容(如一部分位元組碼檔案格式驗證動作)是交叉進行的,載入階段尚未完成,連線階段可能已經開始。
5.2 驗證
驗證是連線階段的第一步,這一階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。
Java語言本身是相對安全的語言,使用Java編碼是無法做到如訪問陣列邊界以外的資料、將一個物件轉型為它並未實現的型別等,如果這樣做了,編譯器將拒絕編譯。但是,Class檔案並不一定是由Java原始碼編譯而來,可以使用任何途徑,包括用十六進位制編輯器(如UltraEdit)直接編寫。如果直接編寫了有害的“程式碼”(位元組流),而虛擬機器在載入該Class時不進行檢查的話,就有可能危害到虛擬機器或程式的安全。
不同的虛擬機器,對類驗證的實現可能有所不同,但大致都會完成下面四個階段的驗證:檔案格式驗證、後設資料驗證、位元組碼驗證和符號引用驗證。
1、檔案格式驗證,是要驗證位元組流是否符合Class檔案格式的規範,並且能被當前版本的虛擬機器處理。如驗證魔數是否0xCAFEBABE;主、次版本號是否正在當前虛擬機器處理範圍之內;常量池的常量中是否有不被支援的常量型別……該驗證階段的主要目的是保證輸入的位元組流能正確地解析並儲存於方法區中,經過這個階段的驗證後,位元組流才會進入記憶體的方法區中儲存,所以後面的三個驗證階段都是基於方法區的儲存結構進行的。
2、後設資料驗證,是對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合Java語言規範的要求。可能包括的驗證如:這個類是否有父類;這個類的父類是否繼承了不允許被繼承的類;如果這個類不是抽象類,是否實現了其父類或介面中要求實現的所有方法……
3、位元組碼驗證,主要工作是進行資料流和控制流分析,保證被校驗類的方法在執行時不會做出危害虛擬機器安全的行為。如果一個類方法體的位元組碼沒有通過位元組碼驗證,那肯定是有問題的;但如果一個方法體通過了位元組碼驗證,也不能說明其一定就是安全的。
4、符號引用驗證,發生在虛擬機器將符號引用轉化為直接引用的時候,這個轉化動作將在“解析階段”中發生。驗證符號引用中通過字串描述的許可權定名是否能找到對應的類;在指定類中是否存在符合方法欄位的描述符及簡單名稱所描述的方法和欄位;符號引用中的類、欄位和方法的訪問性(private、protected、public、default)是否可被當前類訪問
驗證階段對於虛擬機器的類載入機制來說,不一定是必要的階段。如果所執行的全部程式碼確認是安全的,可以使用-Xverify:none引數來關閉大部分的類驗證措施,以縮短虛擬機器類載入時間。
5.3 準備
準備階段是為類的靜態變數分配記憶體並將其初始化為預設值,這些記憶體都將在方法區中進行分配。準備階段不分配類中的例項變數的記憶體,例項變數將會在物件例項化時隨著物件一起分配在Java堆中。
public static int value=123;//在準備階段value初始值為0 。在初始化階段才會變為123 。
5.4 解析
解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程。
符號引用(Symbolic Reference):符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可。符號引用與虛擬機器實現的記憶體佈局無關,引用的目標並不一定已經載入到記憶體中。
直接引用(Direct Reference):直接引用可以是直接指向目標的指標、相對偏移量或是一個能間接定位到目標的控制程式碼。直接引用是與虛擬機器實現的記憶體佈局相關的,如果有了直接引用,那麼引用的目標必定已經在記憶體中存在。
5.5 初始化
類初始化是類載入過程的最後一步,前面的類載入過程,除了在載入階段使用者應用程式可以通過自定義類載入器參與之外,其餘動作完全由虛擬機器主導和控制。到了初始化階段,才真正開始執行類中定義的Java程式程式碼。
初始化階段是執行類構造器<clinit>()方法的過程。<clinit>()方法是由編譯器自動收集類中的所有類變數的賦值動作和靜態語句塊(static{}塊)中的語句合併產生的。
6 題目分析
class SingleTon {
private static SingleTon singleTon = new SingleTon();
public static int count1;
public static int count2 = 0;
private SingleTon() {
count1++;
count2++;
}
public static SingleTon getInstance() {
return singleTon;
}
}
public class Test {
public static void main(String[] args) {
SingleTon singleTon = SingleTon.getInstance();
System.out.println("count1=" + singleTon.count1);
System.out.println("count2=" + singleTon.count2);
}
}
分析:
2:類載入的時候在準備過程中為類的靜態變數分配記憶體並初始化預設值 singleton=null count1=0,count2=0
3:類初始化化,為類的靜態變數賦值和執行靜態程式碼快。singleton賦值為new SingleTon()呼叫類的構造方法
4:呼叫類的構造方法後count=1;count2=1
5:繼續為count1與count2賦值,此時count1沒有賦值操作,所有count1為1,但是count2執行賦值操作就變為0
相關文章
- 【Java面試題】之類載入:從面試題分析Java類載入機制Java面試題
- 由一道面試題理解類載入機制面試題
- 一道面試題搞懂JVM類載入機制面試題JVM
- 面試題總結: 類的載入過程面試題
- 【Android面試-Java-V04】Java類載入過程Android面試Java
- Java類載入機制詳解【java面試題】Java面試題
- 從一道面試題認識函式柯里化面試題函式
- java類的載入過程Java
- 關於Java類載入雙親委派機制的思考(附一道面試題)Java面試題
- Java虛擬機器類載入的過程Java虛擬機
- Java面試題之Java類載入機制詳解!Java面試題
- 虛擬機器類載入機制_類載入的過程虛擬機
- 從一道面試題說開來面試題
- 類載入過程
- 從輸入URL到頁面載入的過程?由一道題完善自己的前端知識體系!前端
- 過程管理的認識與應用(轉載)
- 好程式設計師java分享Java面試寶典:類的載入過程程式設計師Java面試
- 阿里面試題,深入理解Java類載入機制阿里面試題Java
- 兩道面試題帶你解析 Java 類載入機制面試題Java
- 從輸入URL到頁面載入的過程?如何由一道題完善自己的前端知識體系!前端
- JVM類載入過程JVM
- 類的載入過程概述
- 整理類載入的過程
- Java類載入機制與Tomcat類載入器架構JavaTomcat架構
- 虛擬機器類載入機制:類載入時機虛擬機
- JVM之類載入器、載入過程及雙親委派機制JVM
- 類檔案的結構、JVM 的類載入過程、類載入機制、類載入器、雙親委派模型JVM模型
- JVM面試問題系列:Java類載入機制之雙親委派模型JVM面試Java模型
- 從萌新的角度理解 Java 類載入機制Java
- 類載入-載入時機學習
- 類的載入時機
- 【搞定Jvm面試】 面試官:談談 JVM 類載入過程是怎樣的?JVM面試
- java類載入機制Java
- JAVA-大白話探索JVM-類載入過程(二)JavaJVM
- 十二、java知識點——類載入機制(硬貨)Java
- [深入理解Java虛擬機器]第七章 類載入的過程Java虛擬機
- Java 類載入器以及載入機制Java
- JVM(六):探究類載入過程-下JVM