jvm(一)class文件如何加載到jvm

編譯好的class文件如何被加載到jvm中?相信只要去鑽研的小夥伴們都會產生這個疑問,下面我就來談談我個人的理解。
首先,官網上是分爲這麼幾個步驟:加載、鏈接以及初始化。爲了方便理解,我這裏採用圖解來描述。
在這裏插入圖片描述

裝載(Load)

這個裝載我們並不陌生,因爲spring初始化bean之前也會存在這個過程。在裝載裏面其實主要完成以下三件事情:

  1. 通過一個類的全限定名獲取定義此類的二進制字節流。

說白了就是按照類的全路徑去查找該類,並且將類文件轉化爲二進制字節流

  1. 將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構。

這裏如果不瞭解運行時數據區的話,可能一下子沒法理解。簡單來說就是,類的元信息以及Java類裏存在的靜態變量或常量等信息都要保存在方法區裏,所以需要轉化爲符合方法區的數據結構。

  1. 在Java堆中生成一個代表這個類的java.lang.Class對象,作爲對方法區中這些數據的訪問入口。

這裏也需要對運行時數據區有了解,舉個例子,Object obj=new
Object;這裏真實的對象其實存放在Java堆中,而描述類的元信息存放在方法區(這個obj對象是什麼類型、由誰創建等)。對象的對象頭會有個指針指向方法區,以便隨時獲取類的元信息。

鏈接(Link)

從上圖中可以看到,鏈接裏又包含:驗證、準備和解析。

驗證
驗證就是保證被加載類的正確性。比如,文件的格式、元數據、字節碼、符號引用。

準備
爲類的靜態變量分配內存,並將其初始化爲默認值。
舉例:源碼裏存在 static int num=10;
這裏爲num分配空間,並且將值初始化爲默認值,也就是num=0;

解析
把類中的符號引用轉換爲直接引用。class文件裏都是採用符號描述的,這裏需要將符合所代表的含義來轉換成具體的操作。
舉例:某些符合表示需要開闢一段內存空間,那麼根據符合的含義,就需要真的來開闢一段內存空間。

初始化(Initialize)

對類的靜態變量,靜態代碼塊執行初始化操作。上面的準備階段裏已經介紹了需要給靜態變量初始化默認值,而這裏就需要將真實的數據來賦值給變量。
比如:static int num=10;

類加載器ClassLoader

在裝載(Load)階段,其中第1步:通過類的全限定名獲取其定義的二進制字節流,需要藉助類加載器完成。顧名思義,就是用來裝載Class文件的。
但是關於類加載器並不是只有一個,爲了分工明確,它會按照不同功能劃分爲以下四種。
在這裏插入圖片描述
每個類加載器會對應一個區域進行加載。
加載的原則是: 按照自底向上的順序檢查某個類是否已經加載,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個Classloader已經加載了,就表示該類已經被加載,其他Classloader不再繼續加載此類。

思考: 4個類加載器已經分工了,爲什麼還要按照順序一個個往上檢查?
這樣做主要是爲了保證唯一性。這麼來理解:我們比較兩個類是否“相等”,只有保證這兩個類是由同一個類加載器加載的前提下才有意義。否則,即使這兩個類來源於同一個Class文件、被同一個虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。
再通俗點解釋:同一個class文件,它既被App ClassLoader加載,又被Custom ClassLoader加載,那麼我們去instantof的時候,其實是不相等的。而如果出現了這種情況,顯然instantof就沒有存在的必要了。
所以,我們按照自底向上,依次來判斷是否類已經被加載,最終確保類只會被一個類加載器加載,保證它的唯一性。

雙親委派機制

其實上面的加載方式描述的就是一種雙親委派機制,下面來看下它的具體定義。
定義:如果一個類加載器在接到加載類的請求時,它首先不會自己嘗試去加載這個類,而是把這個請求任務委託給父類加載器去完成,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回。只有父類加載器無法完成此加載任務時,才自己去加載。

優勢:Java類隨着加載它的類加載器一起具備了一種帶有優先級的層次關係。比如,Java中的Object類,它存放在rt.jar之中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的啓動類加載器進行加載,因此Object在各種類加載環境中都是同一個類。如果不採用雙親委派模型,由各個類加載器自己去加載的話,那麼系統中會存在多種不同的Object類,這顯然就不合理了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章