首頁 > 科技

還不懂「JVM-類載入」麼?面試官又在問了

2021-08-08 03:05:12

類載入過程

類載入機制:

  • Java虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最 終形成可以被虛擬機器直接使用的Java類型
  • 在Class檔案中述的各類資訊,最終都需要加 載到虛擬機器中之後才能被運行和使用

類的生命週期

  • 一個類型從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期將會經歷
  • 載入、驗證 、 準備、解析、初始化、使用和解除安裝七個階段

  • 載入、驗證、準備、初始化和解除安裝這五個階段的順序是確定的,類型的載入過程必須按 照這種順序按部就班地開始
  • 解析階段在某些情況下可以在初始化階段以後開始 也可以稱為動態繫結
  • 大家可以試想下面這段程式碼輸出什麼?

public class SuperClass {        static {            System.out.println("SuperClass init!"); }        public static int value = 123;}class SubClass extends SuperClass {        static {            System.out.println("SubClass init!"); }    }    class test {        public static void main(String[] args) {            System.out.println(SubClass.value);        }    }

  • 解答:對於靜態欄位, 只有直接定義這個欄位的類才會被初始化,
  • 因此通過其子類來引用父類中定義的靜態欄位,只會觸發 父類的初始化而不會觸發子類的初始化

載入

載入階段需要做的三件事

  1. 通過一個類的全限定名來獲取定義此類的二進位制位元組流。
  2. 將這個位元組流所代表的靜態儲存結構轉化為方法區的運行時資料結構。
  3. 在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口。

驗證

目的

  • 是確保Class檔案的位元組流中包含的資訊符合《Java虛 擬機規範》的全部約束要求
  • 保證這些資訊被當作程式碼運行後不會危害虛擬機器自身的安全。

  1. 檔案格式驗證:驗證魔數開頭、主次版本號、常量池是否支援、Class檔案中各部分是否有被刪除或附加的資訊
  2. 元資料驗證:主要是語義分析、是否有父類、類中欄位是否與父類產生矛盾(如覆蓋了父類的final欄位)
  3. 位元組碼驗證:主要判斷語義是否合法。符合邏輯、保證指令安全
  4. 符號引用驗證:主要判斷該類是否缺少或被禁止訪問它依賴的某些外部類、方法、欄位等資源

準備

目的

  • 正式為類中定義的變數(即靜態變數,被static修飾的變數)分配記憶體並設定類變數初始值的階段
  • 首先是這時候進行記憶體分配的 僅包括類變數,而不包括例項變數,例項變數將會在物件例項化時隨著物件一起分配在Java堆中。
  • 其 次是這裡所說的初始值「 通常情況」下是資料類型的零值,假設一個類變數的定義為:

public static int value = 123;

  • 那變數value在準備階段過後的初始值為0而不是123,因為這時尚未開始執行任何Java方法,
  • 而把 value賦值為123的putstatic指令是程式被編譯後,存放於類構造器()方法之中,所以把value賦值 為123的動作要到類的初始化階段才會被執行
  • Java中所有基本資料類型的零值:

解析

目的

  • Java虛擬機器將常量池內的符號引用替換為直接引用
  • 符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標即可
  • 直接引用(Direct References):直接引用是可以直接指向目標的指針、相對偏移量或者是一個能 間接定位到目標的控制代碼

  1. 類或介面的解析
  2. 欄位解析
  3. 方法解析
  4. 介面方法解析

初始化

目的

  • Java虛擬機器才真正開始執行類中編寫的Java程式程式碼,將主導權移交給應用程式。
  • 進行準備階段時,變數已經賦過一次系統要求的初始零值,而在初始化階段,則會根據程式設計師通 過程式編碼制定的主觀計劃去初始化類變數和其他資源。
  • 我們來看下面的程式碼:

  • 會提示以下錯誤

  • 是因為編譯器收集的順序是由語句在原始檔中出現的順序決定的,靜態語句塊中只能訪問 到定義在靜態語句塊之前的變數定義在它之後的變數,在前面的靜態語句塊可以賦值,但是不能訪問

雙親委派模型

  • 站在Java虛擬機器的角度來看,只存在兩種不同的類載入器:
  • 一種是啟動類載入器(BootstrapClassLoader),另一種是其他所有類的載入器

雙親委派模型的工作過程是

  • 如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把這個請求委派給父類載入器去完成
  • 每一個層次的類載入器都是如此,因此所有的載入請求最終都應該傳送到最頂層的啟動類載入器中,只有當父載入器反饋自己無法完成這個載入請求(它的搜尋範圍中沒有找到所需的類)時
  • 子載入器才會嘗試自己去完成載入。

這樣組織類之間的關係的好處

  • Java中的類隨著它的類 載入器一起具備了一種帶有優先順序的層次關係
  • 例如類java.lang.Object,它存放在rt.jar之中,無論哪一 個類載入器要載入這個類,最終都是委派給處於模型最頂端的啟動類載入器進行載入,
  • 因此Object類 在程式的各種類載入器環境中都能夠保證是同一個類。
  • 反之,如果沒有使用雙親委派模型,都由各個類載入器自行去載入的話,如果使用者自己也編寫了一個名為java.lang.Object的類,並放在程式的 ClassPath中
  • 那系統中就會出現多個不同的Object類,Java類型體系中最基礎的行為也就無從保證,應 用程式將會變得一片混亂。

  • 以上程式碼會報如下的錯誤

錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義為:   public static void main(String[] args)否則 JavaFX 應用程式類必須擴展javafx.application.Application

  • 出現以上錯誤是因為:當執行類載入器的時候,首先會交給父類去載入
  • 如果雙親中的某一個載入器 載入成功後,再向下返回成功
  • 如果所有的雙親和自己都無法載入,則報異常

==如何破壞雙親委派模型==

  • 執行緒上下文類載入器 (Thread Context ClassLoader)。這個類載入器可以通過java.lang.Thread類的setContext-ClassLoader()方 法進行設定,
  • 如果創建執行緒時還未設定,它將會從父執行緒中繼承一個,如果在應用程式的全局範圍內 都沒有設定過的話,那這個類載入器預設就是應用程式類載入器。
  • 有了執行緒上下文類載入器,程式就可以做一些作弊的事情了。JNDI服務使用這個執行緒上下文類
  • 載入器去載入所需的SPI服務程式碼,這是一種父類載入器去請求子類載入器完成類載入的行為,
  • 這種行為實際上是打通了雙親委派模型的層次結構來逆向使用類載入器,已經違背了雙親委派模型的一般性原則

Tomcat的類載入器架構

==基本需求==

  • 部署在同一個伺服器上的兩個Web應用程式所使用的Java類庫可以實現相互隔離
  • 部署在同一個伺服器上的兩個Web應用程式所使用的Java類庫可以互相共享

  • Common類載入器能載入的類都可以被Catalina類載入器和Shared類載入器使 用
  • 而 Catalina類載入器和Shared類載入器自己能載入的類則與對方相互隔離。
  • WebApp類載入器可以使用Shared類載入器載入到的類,但各個WebAp p 類載入器例項之間相互隔離。
  • 而JasperLoader的載入範圍僅僅是這個JSP檔案所編譯出來的那一個Class檔案,
  • 它存在的目的就是為了被 丟棄:當伺服器檢測到JSP檔案被修改時,會替換掉目前的Jasp erLoader的例項
  • 並通過再建立一個新 的JSP類載入器來實現JSP檔案的HotSwap功能。

作者:xiaoff
連結:https://juejin.cn/post/6993118227521880094
來源:掘金


IT145.com E-mail:sddin#qq.com