JVM類加載

JVM類加載

1. Java對象的建立過程

類加載檢查 ===> 分配內存 ===> 初始化零值 ===> 設置對象頭 ===> 執行init方法java

1.1 類加載檢查

虛擬機遇到一條new指令時,首先檢查這個指令的參數是否能在常量池中定位到這個類的符號引用,而且檢查這個符號引用表明的類是否已被加載、解析和初始化過。若是沒有,就必須執行相應的類加載過程。程序員

1.2 分配內存

在類加載檢查經過後,虛擬機將爲新生對象分配內存。web

對象所需的內存大小在類加載完成後即可肯定,爲對象分配空間的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。分配方式包括指針碰撞空閒列表選擇哪一種方式取決於Java堆是否規整,Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。算法

  • 指針碰撞:適用於堆內存規整(沒有內存碎片)的情形。原理:用過的內存整理到一邊,未使用的內存放在另外一邊,中間有一個分界值指針,只須要向着未使用過的內存方向將該指針移動對象內存大小位置便可。採用的GC收集器有Serial和ParNew(由於使用標記-整理,不存在內存碎片)。
  • 空閒列表:適用於堆內存不規整的情形。原理:虛擬機維護一個列表,該列表中記錄哪些內存塊可用,在分配的時候,找一塊足夠大的內存塊來劃分給對象實例,最後更新列表記錄。採用GC收集器爲CMS(由於採用標記-清除算法,堆內存不規整)。

內存分配的併發問題,虛擬機採用兩種方式來保證線程安全。數據庫

  • CAS+失敗重試:CAS是樂觀鎖的一種實現方式。所謂樂觀鎖就是每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。虛擬機採用CAS配上失敗重試的方式保證更新操做的原子性。
  • TLAB:爲每個線程先在Eden區分配一起內存,JVM在給線程中的對象分配內存時,首先在TLAB分配,當對象大於TLAB中剩餘內存或TLAB內存用盡時,再採用CAS進行內存分配。
1.3 初始化零值

內存分配完成後,虛擬機須要將分配到的內存空間都初始化爲零值(不包括對象頭),保證對象的實例字段在Java代碼中能夠不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。tomcat

1.4 設置對象頭

初始化零值以後須要對對象頭進行必要的設置。安全

1.5 執行init方法

上面工做完成後,在虛擬機的視角,新的對象已經產生。從Java程序的視角,執行new指令後就會執行init方法,把對象按照程序員的醫院進行初始化,從而獲得一個真正可用的對象。數據結構

2. 對象訪問定位的方式

①使用句柄:在Java堆中建立句柄池,句柄包含對象實例數據和對象類型數據,本地變量表中的reference存儲對象的句柄地址。併發

②直接指針:本地變量表中存儲的直接就是對象的地址。app

直接指針速度快,而使用句柄的最大好處是reference中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而reference自己不須要修改。

3. Java中Class文件字節碼結構

image-20200811233007896

4. 類加載過程和類的生命週期

類加載過程:加載、鏈接、初始化

類的生命週期:加載、鏈接、初始化、使用、卸載

  • 加載:經過全類名獲取定義此類的字節流;將字節流所表明的靜態存儲結構轉換爲方法去的運行時數據結構;在堆內存中生成一個表明該類的Class對象,做爲方法區這些數據的訪問入口。
  • 鏈接:包括三步:驗證、準備、初始化。①驗證:驗證文件格式、元數據、字節碼符號引用;②準備:爲類的靜態變量分配內存,並將其初始化默認值;③解析:把類中的符號引用轉換爲直接引用。
  • 初始化:維蕾德靜態變量賦予正確的初始值。
  • 使用:new出對象在程序中使用。
  • 卸載:執行垃圾回收。

5. 類加載器

實現經過類的權限定名獲取該類的二進制字節流的代碼塊叫作類加載器。

JVM中內置了三個重要的ClassLoader,除了BootstrapClassLoader,其它類加載器均由Java實現且所有繼承自java.lang.ClassLoader。

  • BootstrapClassLoader(啓動類加載器):最頂層的加載器,由C++實現,負責加載Java核心類庫,即%JAVA_HOME%/lib目錄下的jar包和類。
  • ExtensionClassLoader(擴展類加載器):負載加載Java擴展庫,即%JRE_HOME%/lib/ext目錄下的Jar包和類。
  • AppClassLoader(應用程序加載器):負責加載當前擁有classpath下的全部jar包和類。

6. 雙親委派

6.1 雙親委派機制

每個類都有一個對於它的類加載器。系統中的ClassLoader在協同工做時會默認使用雙親委派機制。

在類加載的時候,首先判斷該類是否被加載,已經加載過的類無需加載會直接返回,負責會本身嘗試加載。加載的時候,首先會把該請求委派給父類加載器進行處理,所以全部的請求最終都會傳送到頂層的啓動類加載BootstrapClassLoader加載器中。當父類加載加載器沒法處理時,纔會本身進行處理。當父類加載器爲null時,會使用BootstrapClassLoader做爲父類加載器。

22

111

6.2 雙親委派機制的做用
  • 避免類的重複加載。即便是相同的類文件,被不一樣的類加載器加載後產生的也是不一樣的兩個類。
  • 保證了核心API不被篡改。
  • 保證了Java程序的穩定運行。
6.3 打破雙親委派機制的方法
  • 自定義一個類加載器,重寫loadClass()方法。
  • 引入線程上下文類加載器
6.4 打破雙親委派機制的場景
6.4.1 JDBC

JDBC:使用線程上下文類加載器(Thread Context ClassLoader)

JDBC的Driver接口定義在JDK中,其實現由數據庫的服務商來提供,好比MySQL驅動包。DriverManager 類中要加載各個實現了Driver接口的類,而後進行管理,其由BootstrapClassLoader加載,而其Driver接口的實現類是位於服務商提供的 Jar 包,根據類加載機制,當被裝載的類引用了另一個類的時候,虛擬機就會使用裝載第一個類的類裝載器裝載被引用的類。也就是說BootstrapClassLoader還要去加載jar包中的Driver接口的實現類。然而BootstrapClassLoader默認只負責加載 $JAVA_HOME中jre/lib/rt.jar 裏全部的類,因此須要由子類加載器去加載Driver實現,這就破壞了雙親委派模型。

6.4.2 Tomcat

Tomcat:自定義類加載器,Tomcat的類加載器以下圖。

33

每一個Tomcat的WebappClassLoader加載本身目錄下的class文件,不會傳遞給父類加載器,破壞了雙親委派機制。

Tomcat自定義了不少來加載器,可能處於如下目的:

  • 對於各個 webapp中的 classlib,須要相互隔離,不能出現一個應用中加載的類庫會影響另外一個應用的狀況,而對於許多應用,須要有共享的lib以便不浪費資源;
  • JVM同樣的安全性問題。使用單獨的 ClassLoader去裝載 Tomcat自身的類庫,以避免其餘惡意或無心的破壞;
  • 熱部署。Tomcat`修改文件不用重啓就會自動從新裝載類庫。
相關文章
相關標籤/搜索