title: 【學習】深入理解JVM之類載入.mddate: 2019-10-20 22:20:06tags: JVM 類載入 Java類的載入,連接,初始化都是在程式運行期間執行的 ## Java 虛擬機與程式的生命周期 1. 執行 System.exit()方法2. 程式正常結束3. 遇到異常或 ...
---
title: 【學習】深入理解JVM之類載入.md
date: 2019-10-20 22:20:06
tags: JVM 類載入
---
Java類的載入,連接,初始化都是在程式運行期間執行的
## Java 虛擬機與程式的生命周期
1. 執行 System.exit()方法
2. 程式正常結束
3. 遇到異常或錯誤終止
4. 由於操作系統或程式虛擬機進程錯誤
以上的情況都可以結束生命周期
## Java 類載入的方式
1. 本地系統直接載入
2. 通過網路下載.class 文件
3. 通過zip,jar 等歸檔文件載入
4. 從專有資料庫中載入.class 文件
5. 通過java 文件動態編譯源文件得到.class 文件
## 類載入器有哪些
- 系統自帶的類載入器
- 根類載入器(Bootstrap ClassLoader)
- 擴展類載入器(Extension ClassLoader)
- 系統類載入器(System ClassLoader)
- 用戶自定義類載入器
- 通過ClassLoader來實現自定義的類載入器
使用java.lang.ClassLoader的子類,通過ClassLoader來實現自定義的類載入器,這個過程中可以定義類的**載入方式**,**載入時機**以及載入過程中做一些事情。
## 類載入過程
**類的載入指的是將類的.class 文件中的二進位數據讀入到記憶體中.將其放在運行時數據區的方法區內,然後在記憶體中創建一個 java.lang.Class 對象(虛擬機規範並未說明Class對象在哪裡,HotSpot 虛擬機將其放在方法區)用來封裝類在方法區內的數據結構**
- 載入
查找並載入類的二進位數據
- 連接
- 驗證
確保被載入的類的**正確性**
- 準備
為類的**靜態變數**分配記憶體, 並將其初始化為**預設值**
- 解析
把類中的**符號引用換做**是**直接引用**
- 初始化
為類的靜態變數賦予**正確的值**
## Java 程式對類的使用方式
所有的 java 虛擬機實現必須在每個類或介面被 Java 程式**首次主動使用**才初始化
-XX:+TraceClassLoading 可以查看類載入
- 主動使用會初始化(7 個)
1. 創建類實例
2. 訪問某個類或介面的靜態變數,或者對該類的靜態變數賦值
3. 調用類的靜態方法
4. 反射(Class.forName("com.test.Test"))
5. 初始化一個類的子類
6. java 虛擬機啟動時,被表明為啟動類的類
7. JDK1.7提供的實例解析結果REF_getStatic,REF_putStatic,REF_invokeStatic句柄對應的類沒有初始化則初始化
- 被動使用初始化
> 對於靜態欄位來說,只有**直接定義了該欄位的類**才會被初始化
~~~java
public class Mytest1 {
public static void main(String[] args) {
System.out.println(Mychirld1.str);
}
}
/**
* 對於靜態欄位來說,只有直接定義了該欄位的類才會被初始化
*/
class Myparent1 {
public static String str = "hello world !";
static {
System.out.println("current in Myparent1");
}
}
class Mychirld1 extends Myparent1{
public static String str2 = "hello world2 !";
static {
System.out.println("current in Mychirld1");
}
}
>>>>>>
current in Myparent1
hello world !
~~~
main 方法里使用子類去調用了父類的靜態變數 因只有直接定義了該欄位的類才會被初始化,所以先初始化列印父類的靜態代碼塊內容,且子類不初始化
> 當一個類初始化的時候,要求其父類全部初始化完畢
這個是主動使用,就會初始化
~~~java
public class Mytest1 {
public static void main(String[] args) {
System.out.println(Mychirld1.str2);
}
}
/**
* 對於靜態欄位來說,只有直接定義了該欄位的類才會被初始化
*/
class Myparent1 {
public static String str = "hello world !";
static {
System.out.println("current in Myparent1");
}
}
class Mychirld1 extends Myparent1{
public static String str2 = "hello world2 !";
static {
System.out.println("current in Mychirld1");
}
}
>>>>>>
current in Myparent1
current in Mychirld1
hello world2 !
~~~
main 方法中調用了子類的靜態變數,主動初始化子類,根據類的載入順序會先初始化父類,所以先列印父類的靜態代碼塊中的東西,在列印子類的.
> 被 final 修飾的變數
當一個main 方法引用了一個類的被 final 修飾的靜態變數會主動觸發其初始化麽?
~~~java
public class Mytest2 {
public static void main(String[] args) {
System.out.println(Myparent2.str);
}
}
class Myparent2 {
public static final String str = "hello world !";
static {
System.out.println("current in Myparent1");
}
}
>>>>>
hello world !
~~~
答案是不會的,
因為被 final 修飾作為一個常量,會在編譯階段,就會被放置在調用該常量的方法所在的類的常量池中.本質上,調用該常量的類和常量定義的類已經沒有直接引用的關係了,我們將編譯時期Myparent2.class 刪除,運行 main 方法也會列印hello world !
>創建數組實例
~~~java
public class Mytest3 {
public static void main(String[] args) {
Myparent3[] myparent3s = new Myparent3[1];
}
}
class Myparent3 {
static {
System.out.println("current in parent3");
}
}
~~~
這種情況什麼輸出都沒有.因為對於數組實例來說,其類型是由 JVM 在運行期動態生成
> 介面初始化
介面中的欄位預設都是public static final
***當一個介面初始化時,並不要求其父介面都完成了初始化只有在真正使用到父介面的時候(如引用到介面中所定義的常量時),才會初始化(但子介面初始化,父介面一定會被載入)***
~~~java
public class Mytest5 {
public static void main(String[] args) {
System.out.println(Mychirld5.b);
}
}
interface Myparent5 {
public static final int a = 7;
}
interface Mychirld5 extends Myparent5 {
public static final int b = new Random().nextInt(2);
}
>>>>
可通過 -XX:+TraceClassLoading查看父介面是否被初始化
~~~
> 助記符
```
/**
* 助記符
*
* idc 表示將 int,float 或是 String 類型的常量值從常量池中推送至棧頂
* bipush 表示將單位元組(-128~127)的常量值推送至棧頂
* sipush 表示將一個短整型常量值(-32768~32767)推送至棧頂
* iconst_1 表示將 int 類型 1 推送至棧頂(iconst_1 - iconst_5)
*/
```
## 總結
類載入的整個過程
開始 ---> 有類載入器載入類 ---> 連接 ----> 初始化 ---> 使用類 ---> 卸載
|---->驗證
| ----> 準備
| ----> 解析
> 載入
就是把二進位類型的 java 類型讀入 java 虛擬機紅
> 連接
驗證:
確保被載入的類的**正確性
準備:
1.為變數分配記憶體,設置預設值 2.在未達到初始化,類變數都沒有初始化為真正的初始值
解析:
在類型的常量池中,尋找類,介面,欄位和方法的符號引用,把這些引用替換為直接引用的過程
> 初始化
為 變數賦初始值
> 類實例化
1.為新的對象分配記憶體 2.為實例變數賦預設值3.為實例變數賦正確的初始值