<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          今天我們來聊聊JVM類加載機(jī)制

          共 6244字,需瀏覽 13分鐘

           ·

          2021-03-09 17:00

          hello 我是寶哥,今天我們來聊聊JVM的類加載過程

          要搞清楚JVM,首先要搞清楚幾個(gè)問題:

          • jvm起到什么作用?
          • 怎么加載class文件的?
          • 加載類時(shí)會(huì)重復(fù)嗎?順序是什么樣的?

          說到j(luò)vm 那么不得不提類的加載過程.我們先來了解下類是如何被一步一步加載到j(luò)vm的

          類的加載過程

          我們先籠統(tǒng)的了解一下類加載的整個(gè)過程:

          如上圖所示,Java源代碼文件(.java后綴)會(huì)被Java編譯器編譯為字節(jié)碼文件(.class后綴)

          然后由JVM中的類加載器加載各個(gè)類的字節(jié)碼文件,加載完畢之后,交由JVM執(zhí)行引擎執(zhí)行。

          在整個(gè)程序執(zhí)行過程中,JVM用一段空間來存儲(chǔ)程序執(zhí)行期間需要用到的數(shù)據(jù)和相關(guān)信息,被稱作為Runtime Data Area(運(yùn)行時(shí)數(shù)據(jù)區(qū)),也就是我們常說的JVM內(nèi)存。

          此時(shí),我們可以片面的理解為:JVM為我們的class字節(jié)碼提供了加載,存儲(chǔ),執(zhí)行的環(huán)境.(jvm是java可跨平臺(tái)運(yùn)行的基石,因?yàn)椴煌南到y(tǒng)有不同的jvm實(shí)現(xiàn),都可以加載.class字節(jié)碼文件)

          Java的類加載機(jī)制

          那么ClassLoader都做了什么呢?

          我們先來看看 類加載機(jī)制的定義:

          虛擬機(jī)把描述類的數(shù)據(jù)從 Class 文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的 Java 類型,這就是虛擬機(jī)的類加載機(jī)制

          這里有幾個(gè)階段比較重要: 1.加載  2.連接  3.初始化

          根據(jù)這3個(gè)階段,我們可以剖析出,類的生命周期:

          類的生命周期

          • 加載: 加載類的二進(jìn)制字節(jié)流,并且將靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),然后在內(nèi)存中生成一個(gè)代表此類的Class對(duì)象,作為方法區(qū)這個(gè)類各種數(shù)據(jù)的訪問入口

          • 驗(yàn)證: 驗(yàn)證是在連接(Linking)部分的第一步,驗(yàn)證的目的是驗(yàn)證Class文件中的字節(jié)流符合當(dāng)前虛擬機(jī)的要求,保證不會(huì)危害虛擬機(jī).

          • 準(zhǔn)備: 為類變量分配內(nèi)存,并且設(shè)置類變量初始值,此時(shí)這此類變量所使用的內(nèi)存都是在方法區(qū)中進(jìn)行分配.

          • 解析: 解析是將符號(hào)引用替換為直接引用,解析動(dòng)作針對(duì)類或接口,字段,類或接口的方法進(jìn)行解析。

          • 初始化:初始化類或接口并且執(zhí)行類或接口的初始化方法,此時(shí),它的生命周期就開始了

            • 虛擬機(jī)規(guī)范規(guī)定了有且只有 5 種情況必須立即對(duì)類進(jìn)行初始化
            • 1.遇到new、getstatic 和 putstatic 或 invokestatic 這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。對(duì)應(yīng)場(chǎng)景是:使用 new 實(shí)例化對(duì)象、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被 final 修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)、以及調(diào)用一個(gè)類的靜態(tài)方法。
            • 2.對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
            • 3.當(dāng)初始化類的父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。(而一個(gè)接口在初始化時(shí),并不要求其父接口全部都完成了初始化)
            • 4.虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含 main() 方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
            • 5.當(dāng)使用 JDK 1.7 的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè) java.lang.invoke.MethodHandle 實(shí)例最后的解析結(jié)果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。
            • 初始化的時(shí)機(jī):
          • 使用: 此時(shí)我們可以通過new關(guān)鍵字,創(chuàng)建實(shí)例對(duì)象.順帶一提,一個(gè)Class對(duì)象總是會(huì)引用它的類加載器。調(diào)用Class對(duì)象的getClassLoader()方法,就能獲得它的類加載器。由此可見,Class實(shí)例和加載它的加載器之間為雙向關(guān)聯(lián)關(guān)系。

          • 卸載: 當(dāng)類不再被引用或被垃圾回收器標(biāo)記為已死對(duì)象時(shí),將會(huì)被回收,但是Java虛擬機(jī)本身會(huì)始終引用類加載器,而這些類加載器則會(huì)始終引用它們所加載的類的Class對(duì)象,因此這些Class對(duì)象始終是可觸及的,也就是說jvm自帶的類加載器所加載的類,在虛擬機(jī)還沒有退出時(shí),始終不會(huì)被卸載,當(dāng)然也有特例 如:我們自己定義的類加載器的類是可以被卸載的.

          ClassLoader 類加載器

          類的唯一性

          任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在Java虛擬機(jī)中的唯一性

          這句定義怎么理解呢?

          兩個(gè)類來源于同一個(gè) Class 文件,被同一個(gè)虛擬機(jī)加載,但是加載它們的類加載器不同,那這兩個(gè)類也不相等

          那有的小伙伴就有疑惑了,還有很多類加載器嗎? emm..那加載的順序呢?會(huì)不會(huì)重復(fù)加載了?

          別急,我們了解下雙親委派原則.

          雙親委派原則

          如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是向上訪問,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒有找到所需的類)時(shí),子加載器才會(huì)嘗試自己去加載。

          這里舉例幾個(gè)面試會(huì)問的classloader職責(zé):

          • Bootstrap ClassLoader:根類加載器,負(fù)責(zé)加載java的核心類,它不是java.lang.ClassLoader的子類,而是由JVM自身實(shí)現(xiàn);
          • Extension ClassLoader:擴(kuò)展類加載器,擴(kuò)展類加載器的加載路徑是JDK目錄下jre/lib/ext,擴(kuò)展類的getParent()方法返回null,實(shí)際上擴(kuò)展類加載器的父類加載器是根加載器,只是根加載器并不是Java實(shí)現(xiàn)的;
          • System ClassLoader:系統(tǒng)(應(yīng)用)類加載器,它負(fù)責(zé)在JVM啟動(dòng)時(shí)加載來自java命令的-classpath選項(xiàng)、java.class.path系統(tǒng)屬性或CLASSPATH環(huán)境變量所指定的jar包和類路徑。程序可以通過getSystemClassLoader()來獲取系統(tǒng)類加載器;
          • User Define ClassLoader: 用戶自定義的classloader,自定義的加載器必須繼承ClassLoader。

          它們的加載順序:

          show me the code:

          // 代碼摘自《深入理解Java虛擬機(jī)》
              protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
                  // 首先,檢查請(qǐng)求的類是否已經(jīng)被加載過了,同時(shí)也解決了小伙伴的疑慮
                  Class c = findLoadedClass(name);
                  if (c == null) {
                      try {
                          if (parent != null) {
                              c = parent.loadClass(name, false);
                          } else {
                              c = findBootstrapClassOrNull(name);
                          }
                      } catch (ClassNotFoundException e) {
                      // 如果父類加載器拋出ClassNotFoundException
                      // 說明父類加載器無法完成加載請(qǐng)求
                      }
                      if (c == null) {
                          // 在父類加載器無法加載的時(shí)候
                          // 再調(diào)用本身的findClass方法來進(jìn)行類加載
                          c = findClass(name);
                      }
                  }
                  if (resolve) {
                      resolveClass(c);
                  }
                  return c;
              }

          那么我們不禁要思考一下為何要用這種原則有何優(yōu)點(diǎn)?

          • 1,一個(gè)當(dāng)然是避免重復(fù)加載,提升性能

          • 2,避免了核心類被用戶篡改(例如我在用戶自定義的classloader中加載一個(gè)String類去覆蓋自帶的String類,由于先讓父類加載,我定義的順序在后.不會(huì)出現(xiàn)覆蓋成功的問題)

          這里有幾個(gè)點(diǎn)小伙伴需要注意,不然要被面試官吊打了:

          1. 類加載器之間的父子關(guān)系不會(huì)以繼承的關(guān)系來實(shí)現(xiàn),他們雖然都繼承于抽象類java.lang.ClassLoader但是他們的關(guān)系是組合關(guān)系,使用組合關(guān)系復(fù)用父類的加載器.
          2. Bootstrap 類加載器是用 C++ 實(shí)現(xiàn)的,不繼承于抽象類java.lang.ClassLoader,它是虛擬機(jī)自身的一部分,如果獲取它的對(duì)象,將會(huì)返回 null;
          3. jvm自帶的類加載器所加載的類,在虛擬機(jī)還沒有退出時(shí),始終不會(huì)被卸載,我們自己定義的類加載器,加載的類是可以被卸載的.

          破壞雙親委派原則

          當(dāng)然類加載器的雙親委派原則是可以被破壞的,破壞它是由于雙親委派模型自身缺陷導(dǎo)致,他沒有辦法解決用戶基礎(chǔ)類又要重新調(diào)用戶類的代碼。為了解決這個(gè)問題就有了線程上下文加載器,例如JNDI、JDBC、JCE等

          舉個(gè)Tomcat的例子:

          每個(gè)Tomcat的webappClassLoader加載自己的目錄下的class文件,不會(huì)傳遞給父類加載器。tomcat之所以造了一堆自己的classloader,大致是出于下面三個(gè)原因:

          • 對(duì)于各個(gè) webapp中的 class和 lib,需要相互隔離,不能出現(xiàn)一個(gè)應(yīng)用中加載的類庫(kù)會(huì)影響另一個(gè)應(yīng)用的情況,而對(duì)于許多應(yīng)用,需要有共享的lib以便不浪費(fèi)資源。
          • 與 jvm一樣的安全性問題。使用單獨(dú)的 classloader去裝載 tomcat自身的類庫(kù),以免其他惡意或無意的破壞;
          • 熱部署。相信大家一定為 tomcat修改文件不用重啟就自動(dòng)重新裝載類庫(kù)而驚嘆吧。

          破壞雙親委派的方式

          雙親委派機(jī)制原則在loadclass方法中。只需要繞開loadclass方法中即可。

          1. 自定義類加載器 ,重寫loadclass方法

          2. 使用SPI機(jī)制繞開loadclass 方法。當(dāng)前線程設(shè)定關(guān)聯(lián)類加載器

          關(guān)于SPI在我另外一篇文章https://mp.weixin.qq.com/s/2UFHJ_i09APy-VS8oeiUIQ


          講了那么久的加載,此時(shí)我們才剛剛一只腳踏進(jìn)JVM的大門,后面我們將分析JVM運(yùn)行時(shí)數(shù)據(jù)區(qū).大家持續(xù)關(guān)注java寶典公眾號(hào),我們下章再聊

          最近我創(chuàng)建了一個(gè)學(xué)習(xí)群,有熱愛學(xué)習(xí)的小伙伴可以進(jìn)群討論~


          JAVA的SPI機(jī)制


          JVM生成的這3種文件,你都見過嗎?




          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩美女黄片 | 日韩中文欧美 | 538国产视频 | 911偷拍网在线偷拍 | 日韩AV无码电影 |