前言
之前說了類加載的程序,但是有的讀者表示還是有些知識點沒弄清楚,相關面試題也不能思考出結果,所以今天就來總結下類加載、物件實體化方面的知識點/面試題,幫助大家加深印象,
全是干貨,一網打盡類的基礎知識!先看看下面的問題都能回答上來嗎?
- 描述new一個物件的程序,并結合例子說明,
- 類初始化的觸發時機,
- 多執行緒進行類的初始化會出問題嗎?
- 類的實體化觸發時機,
<clinit>()方法和<init>()方法區別,- 在類都沒有初始化完畢之前,能直接進行實體化相應的物件嗎?
- 類的初始化程序與類的實體化程序的異同?
- 一個實體變數在物件初始化的程序中會被賦值幾次?
描述new一個物件的程序
先上圖,再描述:



Java中物件的創建程序包括 類初始化和類實體化兩個階段,
而new就是創建物件的一種方式,一種時機,
當執行到new的位元組碼指令的時候,會先判斷這個類是否已經初始化,如果沒有初始化就要進行類的初始化,也就是執行類構造器<clinit>()方法,
如果已經初始化了,就直接進行類物件的實體化,
類的初始化,是類的生命周期中的一個階段,會為類中各個類成員賦初始值,類的實體化,是指創建一個類的實體的程序,
但是在類的初始化之前,JVM會保證類的裝載,鏈接(驗證、準備、決議)四個階段都已經完成,也就是上面的第一張圖,
裝載是指Java虛擬機查找.class檔案并生成位元組流,然后根據位元組流創建java.lang.Class物件的程序,鏈接是指驗證創建的類,并將其決議到JVM中使之能夠被JVM執行,
那到底類加載的時機是什么時候呢?JVM 并沒有規范何時具體執行,不同虛擬機的實作會有不同,常見有以下兩種情況:
隱式裝載:在程式運行程序中,當碰到通過new等方式生成物件時,系統會隱式呼叫ClassLoader去裝載對應的 class 到記憶體中;顯示裝載:在撰寫源代碼時,主動呼叫Class.forName()等方法也會進行 class 裝載操作,這種方式通常稱為顯示裝載,
所以到這里,大的流程框架就搞清楚了:
-
當
JVM碰到new位元組碼的時候,會先判斷類是否已經初始化,如果沒有初始化(有可能類還沒有加載,如果是隱式裝載,此時應該還沒有類加載,就會先進行裝載、驗證、準備、決議四個階段),然后進行類初始化, -
如果已經初始化過了,就直接開始類物件的
實體化作業,這時候會呼叫類物件的<init>方法,
結合例子說明
然后說說具體的邏輯,結合一段類代碼:
public class Run {
public static void main(String[] args) {
new Student();
}
}
public class Person{
public static int value1 = 100;
public static final int value2 = 200;
public int value4 = 400;
static{
value1 = 101;
System.out.println("1");
}
{
value1 = 102;
System.out.println("3");
}
public Person(){
value1 = 103;
System.out.println("4");
}
}
public class Student extends Person{
public static int value3 = 300;
public int value5 = 500;
static{
value3 = 301;
System.out.println("2");
}
{
value3 = 302;
System.out.println("5");
}
public Student(){
value3 = 303;
System.out.println("6");
}
}
-
首先是類裝載,鏈接(驗證、準備、決議),
-
當執行類準備程序中,會對類中的
靜態變數分配記憶體,并設定為初始值也就是“0值”,比如上述代碼中的value1,value3,會為他們分配記憶體,并將其設定為0,但是注意,用final修飾靜態常量value2,會在這一步就設定好初始值102, -
初始化階段,會執行類構造器
<clinit>方法,其主要作業就是初始化類中靜態的(變數,代碼塊),但是在當前類的<clinit>方法執行之前,會保證其父類的<clinit>方法已經執行完畢,所以一開始會執行最上面的父類Object的<clinit>方法,這個例子中會先初始化父類Person,再初始化子類Student, -
初始化中,靜態變數和靜態代碼塊順序是由陳述句在源檔案中出現的順序所決定的,也就是誰寫在前面就先執行誰,所以這里先執行父類中的
value1=100,value1 = 101,然后執行子類中的value3 = 300,value3 = 301, -
接著就是創建物件的程序,也就是類的實體化,當物件被類創建時,虛擬機會
分配記憶體來存放物件自己的實體變數和父類繼承過來的實體變數,同時會為這些事例變數賦予默認值(0值), -
分配完記憶體后,會初始化父類的普通成員變數
(value4 = 400),和執行父類的普通代碼塊(value1=102),順序由代碼順序決定, -
執行父類的建構式
(value1 = 103), -
父類實體化完了,就實體化子類,初始化子類的普通成員變數
(value5 = 500),執行子類的普通代碼塊(value3 = 302),順序由代碼順序決定, -
執行子類的建構式
(value3 = 303),
所以上述例子列印的結果是:
123456
總結一下執行流程就是:
-
父類靜態變數和靜態代碼塊;
-
子類靜態變數和靜態代碼塊;
-
父類普通成員變數和普通代碼塊;
-
父類的建構式;
-
子類普通成員變數和普通代碼塊;
-
子類的建構式,
最后,大家再結合流程圖好好梳理一下:



類初始化的觸發時機
在同一個類加載器下,一個型別只會被初始化一次,剛才說到new物件是類初始化的一個判斷時機,其實一共有六種能夠觸發類初始化的時機:
-
虛擬機啟動時,初始化包含
main方法的主類; -
遇到
new等指令創建物件實體時,如果目標物件類沒有被初始化則進行初始化操作; -
當遇到訪問靜態方法或者靜態欄位的指令時,如果目標物件類沒有被初始化則進行初始化操作;
-
子類的初始化程序如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化;
-
使用反射
API進行反射呼叫時,如果類沒有進行過初始化則需要先觸發其初始化; -
第一次呼叫
java.lang.invoke.MethodHandle實體時,需要初始化MethodHandle指向方法所在的類,
多執行緒進行類的初始化會出問題嗎
不會,<clinit>()方法是阻塞的,在多執行緒環境下,如果多個執行緒同時去初始化一個類,那么只會有一個執行緒去執行這個類的<clinit>(),其他執行緒都會被阻塞,
類的實體化觸發時機
- 使用
new關鍵字創建物件 - 使用Class類的
newInstance方法,Constructor類的newInstance方法(反射機制) - 使用
Clone方法創建物件 - 使用(反)序列化機制創建物件
<clinit>()方法和<init>()方法區別,
-
<clinit>()方法發生在類初始化階段,會執行類中的靜態類變數的初始化和靜態代碼塊中的邏輯,執行順序就是陳述句在源檔案中出現的順序, -
<init>()方法發生在類實體化階段,是默認的建構式,會執行普通成員變數的初始化和普通代碼塊的邏輯,執行順序就是陳述句在源檔案中出現的順序,
在類都沒有初始化完畢之前,能直接進行實體化相應的物件嗎?
剛才都說了先初始化,再實體化,如果這個問題可以的話那不是打臉了嗎?
沒錯,要打臉了哈哈,
確實是先進行類的初始化,再進行類的實體化,但是如果我們在類的初始化階段就直接實體化物件呢?比如:
public class Run {
public static void main(String[] args) {
new Person2();
}
}
public class Person2 {
public static int value1 = 100;
public static final int value2 = 200;
public static Person2 p = new Person2();
public int value4 = 400;
static{
value1 = 101;
System.out.println("1");
}
{
value1 = 102;
System.out.println("2");
}
public Person2(){
value1 = 103;
System.out.println("3");
}
}
嘿嘿,這時候該怎么列印結果呢?
按照上面說過的邏輯,應該是先靜態變數和靜態代碼塊,然后普通成員變數和普通代碼塊,最后是建構式,
但是因為靜態變數又執行了一次new Person2(),所以實體化程序被強行提前了,在初始化程序中就進行了實體化,這段代碼的結果就變成了:
23123
所以,實體化不一定要在類初始化結束之后才開始初始化,有可能在初始化程序中就進行了實體化,
類的初始化程序與類的實體化程序的異同?
學了上面的內容,這個問題就很簡單了:
-
類的初始化,是指在類裝載,鏈接之后的一個階段,會執行<clinit>()方法,初始化靜態變數,執行靜態代碼塊等,只會執行一次, -
類的實體化,是指在類完全加載到記憶體中后創建物件的程序,會執行<init>()方法,初始化普通變數,呼叫普通代碼塊,可以被呼叫多次,
一個實體變數在物件初始化的程序中最多可以被賦值幾次?
那我們就試試舉例出最多的情況,其實也就是每個要經過的地方都對實體變數進行一次賦值:
- 1、
物件被創建時候,分配記憶體會把實體變數賦予默認值,這是肯定會發生的, - 2、
實體變數本身初始化的時候,就給他賦值一次,也就是int value1=100, - 3、
初始化代碼塊的時候,也賦值一次, - 4、
建構式中,在進行賦值一次,
一共四次,看代碼:
public class Person3 {
public int value1 = 100;
{
value1 = 102;
System.out.println("2");
}
public Person3(){
value1 = 103;
System.out.println("3");
}
}
參考
https://blog.csdn.net/justloveyou_/article/details/72466416
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1860
https://www.jianshu.com/p/8a14ed0ed1e9
拜拜
有一起學習的小伙伴可以關注下?? 我的公眾號——碼上積木,每天剖析一個知識點,我們一起積累知識,公眾號回復111可獲得面試題《思考與解答》以往期刊,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/244667.html
標籤:Android
上一篇:listview點擊跳轉傳值問題
下一篇:類加載、物件實體化知識點一網打盡

