天天看點

深入java虛拟機學習 -- 類的加載機制

當看到“類的加載機制”,肯定很多人都在想我平時也不接觸啊,工作中無非就是寫代碼,不會了可以百度,至于類,jvm是怎麼加載的我一點也不需要關心。在我剛開始工作的時候也覺得這些底層的内容距離我還很遙遠,根本不用關系,可是随着做開發的時間越來越長,慢慢的已經意識到虛拟機相關要領的重要性。一直想寫篇和虛拟機相關的文章,無奈之前由于各種原因一直沒寫,年初工作還不是很忙,抽時間來慢慢完善jvm的内容。

好了,不說廢話了,大家一起來看下代碼吧,都來猜猜下面的兩份代碼的執行結果是什麼?

1 class Singleton
 2 {
 3     private static Singleton singleton = new Singleton();
 4     public static int counter1;
 5     public static int counter2=0;
 6 
 7     private Singleton(){
 8         counter1++;
 9         counter2++;
10     }
11 
12     public static Singleton getInstance(){
13         return singleton;
14     }
15 }
16 
17 public class Demo
18 {
19    public static void main(String[] args){
20        Singleton singleton=Singleton.getInstance();
21        System.out.println("counter1:"+singleton.counter1);
22        System.out.println("counter2:"+singleton.counter2);
23     }
24 }      
深入java虛拟機學習 -- 類的加載機制
1 class Singleton
 2 {
 3     public static int counter1;
 4     public static int counter2=0;
 5     private static Singleton singleton = new Singleton();
 6 
 7     private Singleton(){
 8         counter1++;
 9         counter2++;
10     }
11 
12     public static Singleton getInstance(){
13         return singleton;
14     }
15 }
16 
17 public class Demo
18 {
19    public static void main(String[] args){
20        Singleton singleton=Singleton.getInstance();
21        System.out.println("counter1:"+singleton.counter1);
22        System.out.println("counter2:"+singleton.counter2);
23     }
24 }      
深入java虛拟機學習 -- 類的加載機制

看到這樣的結果,不知道你有沒有意外呢?看完我的這幾篇文章後聰明的你一看就明白是怎麼回事了。

java虛拟機與程式的生命周期

  - 執行了System.exit()方法

  - 程式正常執行結束

  - 程式在執行過程中遇到了異常或錯誤而終止

  - 由于作業系統出現錯誤導緻java虛拟機程序終止

在如上幾種情況下,java虛拟機将結束自己的生命周期

類的生命周期

類從被加載到虛拟機記憶體中開始,到解除安裝出記憶體為止,它的整個生命周期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和解除安裝(Unloading)7個階段。其中準備、驗證、解析3個部分統稱為連接配接(Linking)。如圖所示。

深入java虛拟機學習 -- 類的加載機制

加載、驗證、準備、初始化和解除安裝這5個階段的順序是确定的,類的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之後再開始,這是為了支援Java語言的運作時綁定(也稱為動态綁定或晚期綁定)。

加載

查找并加載類的二進制資料,加載是類加載過程中的第一個階段,在加載階段,虛拟機需要完成以下三件事情:

  • 類的加載指的是将類的.class檔案中的二進制資料讀取到記憶體中
  • 将其放在運作時資料區的方法區内,
  • 然後在堆區建立一個java.lang.Class對象,用來封裝類在方法區内的資料結構
深入java虛拟機學習 -- 類的加載機制

類加載的最終産品書位于堆區中的Class對象

Class對象封裝了類在方法區内的資料結構,并像java程式員提供了通路方法去内的資料結構的接口。

Java程式對類的使用方式可以分為兩種:

-主動使用(六種)

1,建立類的執行個體

2,通路某個類或接口的靜态變量,或者對該靜态變量指派

3,調用類的靜态方法

4,反射(Class.forName(“com.jack.test”))

5,初始化一個類的子類

6,Java虛拟機啟動時被标明為啟動類的類

-被動使用(除以上6種情況外,其他情況均為被動使用)

類加載器并不需要等到某個類被“首次主動使用”時再加載它,JVM規範允許類加載器在預料某個類将要被使用時就預先加載它,如果在預先加載的過程中遇到了.class檔案缺失或存在錯誤,類加載器必須在程式首次主動使用該類時才報告錯誤(LinkageError錯誤),如果這個類一直沒有被程式主動使用,那麼類加載器就不會報告錯誤。

加載.class檔案的方式:

  • 從本地系統中直接加載
  • 通過網絡下載下傳.class檔案
  • 從zip,jar等歸檔檔案中加載.class檔案
  • 從專有資料庫中提取.class檔案
  • 将Java源檔案動态編譯為.class檔案

類加載器

不同的類加載器負責的元件不同,可分為2種類型

  • 自定義類加載器(java.lang.classLoader的自來)
  • java虛拟機自帶類加載器

java虛拟機自帶類加載器按類型又可分為三種類型:

  • 啟動類加載器:Bootstrap ClassLoader    C/C++實作的  負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,并且能被虛拟機識别的類庫(如rt.jar,所有的java.開頭的類均被Bootstrap ClassLoader加載)。啟動類加載器是無法被Java程式直接引用的。
  • 擴充類加載器:Extension ClassLoader    使用java代碼實作 該加載器由sun.misc.Launcher$ExtClassLoader實作,它負責加載JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.開頭的類),開發者可以直接使用擴充類加載器。
  • 應用程式類加載器:Application ClassLoader    使用java代碼實作 該類加載器由sun.misc.Launcher$AppClassLoader來實作,它負責加載使用者類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程式中沒有自定義過自己的類加載器,一般情況下這個就是程式中預設的類加載器。

雙親委派模型

雙親委派模型的工作流程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,依次向上,是以,所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,隻有當父加載器在它的搜尋範圍中沒有找到所需的類時,即無法完成該加載,子加載器才會嘗試自己去加載該類。

雙親委派機制:

  1. 當AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。
  2. 當ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。
  3. 如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib裡未查找到該class),會使用ExtClassLoader來嘗試加載;
  4. 若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。

雙親委派模型意義:

  • 系統類防止記憶體中出現多份同樣的位元組碼
  • 保證Java程式安全穩定運作

層次關系如下圖:

深入java虛拟機學習 -- 類的加載機制

驗證

類被加載後,就進入連接配接階段。連接配接就是将已經讀取到記憶體的類的二進制資料合并到虛拟機的運作時環境中去

深入java虛拟機學習 -- 類的加載機制

準備

在準備階段,Java虛拟機為類的靜态變量配置設定記憶體,并設定預設的初始值,這些記憶體都将在方法區中配置設定。

對于該階段有以下幾點需要注意:

  • 這時候進行記憶體配置設定的僅包括類變量(static),而不包括執行個體變量,執行個體變量會在對象執行個體化時随着對象一塊配置設定在Java堆中。
  • 這裡所設定的初始值通常情況下是資料類型預設的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。

例如對一下Sample類,在準備階段,将為int類型的靜态變量a配置設定4個位元組的記憶體控件,并賦予預設值0,為long類型的靜态變量b配置設定8個位元組的記憶體控件,并賦予預設值0;

public class Sample{
    private static int a=1;
    private static int b;

    static{
       b=2;
    }
}      

解析

在解析階段,Java虛拟機會把類的二進制資料中的符号引用替換為直接引用。例如在Worker類的gotoWork()方法中會引用Car類的run()方法。

public void gotoWork(){
   car.run(); //這段代碼在Worker類的二進制資料中表示為符号引用
}      

在Worker類的二進制資料中,包含了一個對Car類的run()方法的符号引用,它由run()方法的全名和相關描述符組成。在解析階段,Java虛拟機會把這個符号引用替換為一個指針,該指針指向Car類的run()方法區内的記憶體位置,這個指針就是直接引用。

初始化

類初始化階段是類加載過程的最後一步,到了初始化階段,才真正開始執行類中定義的java程式代碼。在初始化階段,Java虛拟機執行類的初始化語句,為類的靜态變量 賦予正确的初始值。在程式中,靜态變量的初始化有兩種途徑:

  • 在靜态變量的聲明處進行初始化
  • 在靜态代碼庫中進行初始化

在如下代碼中,靜态變量a和b都被顯示初始化,而靜态變量c沒有被顯示初始化,它将保持預設值0;

public class Sample{
    private static int a=1;
    private static int b;
    private static int c;

    static{
       b=2;
    }
}      

JVM初始化步驟:

  1. 假如這個類還沒有被加載和連接配接,則程式先加載并連接配接該類
  2. 假如該類的直接父類還沒有被初始化,則先初始化其直接父類
  3. 假如類中有初始化語句,則系統依次執行這些初始化語句

開開心心編碼,快快樂樂生活。