類的加載過程
(一)簡述類加載過程:
類加載過程: JVM虛擬機把.class文件中類信息加載進內存
.class文件: 通過javac命令將java文件編譯成字節碼 ,此時生成的字節碼文件稱為.class文件
類加載的通俗舉例: JVM在執行某段代碼時,遇到了class A,此時內存中並沒有class A的相關信息 ,JVM就會到相應的class文件中去尋找class A的類信息,並加載進內存中 。
注: JVM不是一開始就把所有的類都加載進內存中, 而是只有第一次遇到某個需要運行的類時才會加載,且只加載一次
(二)Java虛擬機中類加載的全過程
參考:https://blog.csdn.net/ln152315/article/details/79223441
https://blog.csdn.net/qq_31156277/article/details/80188110
1)加載
1.1、將.class字節碼通過類加載器加載到內存
.class字節碼來源: 本地路徑下編譯生成的.class文件 ,從jar包中的.class文件 ,從遠程網絡,以及動態
代理實時編譯。
類加載器的分類:啟動類加載器、擴展類加載器 、應用類加載器 、用戶的自定義類加載器 。
類加載的三個階段:
1、通過一個類的全限定名 (例:java/lang/Thread格式,使用 / 相隔) 獲取定義此類的二進制字節流
2、將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構
3、在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口
2)驗證
確保 Class文件的字節流中包含的 信息符合jvm虛擬機的要求,並且不會危害虛擬機自身的安全
驗證的動作:
2.1、文件格式的驗證(基於二進制流進行)
驗證字節流是否符合Class文件格式的規范 ,保證輸入的字節流能正確地解析並存儲於方法區之內
注: 驗證是基於二進制字節流進行的 ,只有通過了這個階段的驗證后,字節流才能進入內存中的方法
區進行存儲。
例:常量中是否有不被支持的常量?文件中是否有不規范的或者附加的其他信息?
2.2、元數據驗證(基於方法區的存儲結構 )
對字節碼描述的信息進行語義分析 ,使其符合Java語言規范
例:該類是否繼承了被final修飾的類?類中的字段,方法是否與父類沖突?是否出現了不合理的重載?
2.3、字節碼驗證(基於方法區的存儲結構 )
對類的方法體進行校驗分析 ,保證被校驗類的方法在運行時不會做出危害虛擬機安全的事件
通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的
例:
2.4、符號引用驗證(基於方法區的存儲結構 )
校驗符號引用中通過全限定名是否能夠找到對應的類?校驗符號引用中的訪問性(private,public等)
是否可被當前類訪問
注:驗證與加載交替執行,單是加載順序總是優於驗證執行
3)准備
為類變量分配內存並設置類變量初始值 ,這些變量所使用的內存都將在方法區中進行分配
類變量:static修飾的變量
類變量初始值:public static int value=123; 在准備階段value的初始值為0.而不是123
public static final int value=123;, 當被final修飾事物類變量 准備階段的初始值為123
4)解析
將常量池內的符號引用替換為直接引用的過程 、
符號引用:即一個字符串,但是這個字符串給出了一些能夠唯一性識別一個方法,一個變量,一個類的相關信息
直接引用:可以理解為一個內存地址,或者一個偏移量
即把所有的類名,方法名,字段名這些符號引用替換為具體的內存地址或偏移量,也就是直接引用
解析內容:
- 類或接口
- 字段
- 類方法
- 接口方法
- 方法類型
- 方法句柄
- 調用點限定符
驗證、准備、解析統稱為連接
5)初始化
執行類構造器
對類變量初始化 ,執行類構造器 ,只對static修飾的變量或語句進行初始化
初始化的順序:
(三)類的實例創建過程
參考:https://blog.csdn.net/qq_38537709/article/details/88750605
1.當創建子類實例時,先對父類的類變量(satic修飾的變量)和 static{}代碼塊進行初始化,再對子類的類變量(satic修飾的變量)和 static{}代碼塊進行初始化
2.對子父類完成初始化后調用構造器,子類的構造器第一行隱式的調用了父的空的構造器,在對父類構造器初始化
時先為成員變量分配內存空間,再對構造器進行初始化
3.成員變量的賦值優先於構造方法里的語句。
class C{
C() {
System.out.println("正執行SuperClass類的構造方法對成員變量初始化,為其成員變量C分配內存空間");
}
}
class SuperClass {
C c = new C();
static{
System.out.println("SuperClass類 正在初始化");
}
SuperClass() {
this("正在調用SuperClass的有參構造方法");
System.out.println("正在執行SuperClass的無參構造方法");
}
SuperClass(String s) {
System.out.println(s);
}
}
public class SubClass extends SuperClass{
C c = new C();
static{
System.out.println("SubClass類 正在初始化");
}
SubClass() {
/*在子類構造方法的第一句,隱式的調用父類的構造方法;*/
System.out.println("正在執行子類SubClass的構造方法");
}
public static void main(String[] args) {
new SubClass();
}
}
執行結果:
SuperClass類 正在初始化
SubClass類 正在初始化
正執行SuperClass類的構造方法對成員變量初始化,為其成員變量C分配內存空間
正在調用SuperClass的有參構造方法
正在執行SuperClass的無參構造方法
正執行SuperClass類的構造方法對成員變量初始化,為其成員變量C分配內存空間
正在執行子類SubClass的構造方法
當子類調用父類的靜態字段時只有定義這個字段的類即父類才進行初始化,子類不初始化
父類
public class SuperClass {
public static int value;
public SuperClass() {
}
static {
System.out.println("SuperClass Init");
value = 123;
}
}
子類
public class SubClass extends SuperClass {
public SubClass() {
}
static {
System.out.println("SubClass Init");
}
}
測試:
public class Test {
public Test() {
}
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
結果:
SuperClass Init
123
使用類調用一個static final修飾的常量,該類不會進行初始化
public class ConstantClass {
static{
System.out.println("ConstantClass Init");
}
public static final String HELLOWOELD="hello world";
}
public class NotInitialization {
public static void main(String[] args) {
System.out.println(ConstantClass.HELLOWOELD);
}
}
輸出結果:
hello world
當創建一個類類型的數組時,該類不會進行初始化
public class SupperClass {
static{
System.out.println("SuperClass Init");
}
public static int value=123;
}
public class SubClass {
public static void main(String[] args) {
SupperClass[] supperClasses = new SupperClass[10];
}
}
結果
無輸出結果:
總結:
一個類加載過程包含5個過程
一):加載, 首先將編譯過的字節碼文件通過類加載器加載入內存,將類轉為二進制流的形式,將靜態時的存儲結構轉為 方法區運行時的額數據結構,並生成java.lang.class對象
二):連接,連接的過程主要包括驗證、准備、解析三個步驟
驗證的過程是檢驗二進制流是否符合java規范,是否對jvm產生傷害,驗證分為文件格式驗證(驗證二進制流是否符合.class文件規范),元數據驗證(對語義進行分析),字節碼驗證(對方法體進行校驗分析),符號引用驗檢驗是否能通過類名找到類對象,以及類的修飾符(private、public)
准備的過程是對類變量賦默認初值
解析的過程是將常量池內的符號引用替換為直接引用的過程(將間接引用轉為直接引用; 即將變量名稱轉為對應的地址值或偏移量)
三):初始化,初始化是為類變量賦值+執行靜態語句塊 static{}的過程
不會引發類初始化的3種情況
1.子類引用父類的類變量,子類不會進行初始化,父類進行初始化
2.一個類引用本類的常量即 static fianl修飾的變量不會進行初始化
3.聲明一個類類型數組時也不會進行初始化
子類進行實例化的過程
1.父類先初始化,子類再初始化(先給父類的類變量賦初值+執行靜態塊static{},再執行子類的)
2.先執行父類的構造方法,在執行子類的構造方法
3.在執行構造方法前先對類的成員變量進行賦值