<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 是如何加載 Java 類(lèi)的?

          共 3534字,需瀏覽 8分鐘

           ·

          2020-11-25 12:13

          4a4758403fcaf575c690e727bd4b6e67.webp


          9b06342314e08f23122f6007cb054c56.webp


          看到這個(gè)題目的時(shí)候,你可能就會(huì)覺(jué)得,阿粉,這不是挺簡(jiǎn)單的一個(gè)問(wèn)題么

          如何加載?不就是 加載,鏈接,初始化 這三步嘛,說(shuō)白了不就是類(lèi)加載過(guò)程么

          那么,你知道這三步具體又做了什么嘛?這就是本篇文章想要寫(xiě)的

          加載

          加載的過(guò)程,就是查找字節(jié)流,并根據(jù)查找到的字節(jié)流來(lái)創(chuàng)建類(lèi)的一個(gè)過(guò)程

          Java 語(yǔ)言的類(lèi)型可以分成兩大類(lèi):基本類(lèi)型和引用類(lèi)型。基本類(lèi)型就是由 JVM 預(yù)先定義好的,所以也就沒(méi)有查找字節(jié)流這一說(shuō)了

          對(duì)于引用類(lèi)型來(lái)說(shuō)的話,又可以細(xì)分為四種:類(lèi),接口,數(shù)組類(lèi)和泛型參數(shù)。因?yàn)榉盒蛥?shù)在編譯過(guò)程中會(huì)被擦除,所以在 JVM 中就只有前三種。而數(shù)組類(lèi)又是由 JVM 直接生成的,所以查找字節(jié)流的話,就只有類(lèi)和接口了。

          那么 JVM 是怎么查找字節(jié)流的呢?如果你對(duì)這塊內(nèi)容比較熟的話,應(yīng)該就能想起來(lái)類(lèi)加載器,它主要有四類(lèi):?jiǎn)?dòng)類(lèi)加載器,擴(kuò)展類(lèi)加載器,應(yīng)用程序類(lèi)加載器和用戶(hù)自定義類(lèi)加載器

          這塊又有個(gè)知識(shí)點(diǎn)就是雙親委派機(jī)制:大概就是如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求,首先不會(huì)自己去加載這個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成。通過(guò)雙親委派機(jī)制就能保證同樣一個(gè)類(lèi)只被加載一次

          經(jīng)過(guò)類(lèi)加載器之后,這個(gè)類(lèi)就算是加載進(jìn)來(lái)了

          鏈接

          對(duì)鏈接過(guò)程而言, jvm 實(shí)現(xiàn)具有靈活性,但必須保留下列屬性:

          1、在鏈接之前,類(lèi)或者接口必須已經(jīng)被完全加載;

          2、在初始化之前,類(lèi)或者接口必須已經(jīng)被完全驗(yàn)證和準(zhǔn)備;

          3、鏈接過(guò)程中檢測(cè)到的程序錯(cuò)誤會(huì)拋出到程序中某個(gè)位置,在該位置上,程序?qū)⒉扇∧承┎僮鳎@些操作可能會(huì)直接或間接地鏈接到類(lèi)或者接口所涉及到的類(lèi)或者接口。

          鏈接這塊又分為三部分:驗(yàn)證,準(zhǔn)備,解析

          驗(yàn)證階段就是想要看看 class 文件的前 8 位是不是 java 標(biāo)識(shí)符,想看看符不符合規(guī)范什么的

          準(zhǔn)備階段就是給靜態(tài)字段分配內(nèi)存。除了分配內(nèi)存之外,部分 JVM 還會(huì)在此階段構(gòu)造其他跟類(lèi)層次相關(guān)的數(shù)據(jù)結(jié)構(gòu),比如說(shuō)用來(lái)實(shí)現(xiàn)虛方法的動(dòng)態(tài)綁定的方法表,這個(gè)方法表是用來(lái)解決動(dòng)態(tài)綁定的問(wèn)題的,解析時(shí)通過(guò)這個(gè)方法表,根據(jù)實(shí)際類(lèi)型來(lái)解析獲取對(duì)應(yīng)的方法。

          在 class 文件被加載到 JVM 之前,這個(gè)類(lèi)沒(méi)辦法知道其他類(lèi)和方法,字段所對(duì)應(yīng)的具體地址,甚至都不知道自己的方法,字段的地址,所以如果需要引用這些成員時(shí), Java 編譯器就會(huì)生成一個(gè)符號(hào)引用,在運(yùn)行階段,這個(gè)符號(hào)引用一般都可以準(zhǔn)確的定位到具體目標(biāo)上

          解析階段主要就是將符號(hào)引用解析成實(shí)際引用。如果符號(hào)引用指向一個(gè)未被加載的類(lèi),或者沒(méi)有被加載類(lèi)的字段或方法,此時(shí)解析階段就會(huì)觸發(fā)這個(gè)類(lèi)的加載(但不一定會(huì)觸發(fā)這個(gè)類(lèi)的鏈接以及初始化)

          在解析階段,不同的 JVM 有不同的解析策略,例如:

          public?class?A?{
          ??public?void?main(String?args[])?{
          ????B?b?=?null;
          ??}
          }

          策略 1 :鏈接 A 的時(shí)候發(fā)現(xiàn)引用了 B,因此加載 B

          策略 2 :鏈接 A 的時(shí)候發(fā)現(xiàn)引用了 B,但是 B 沒(méi)有被使用,所以暫時(shí)不加載 B。在真正使用 B 的時(shí)候才進(jìn)行加載,比如?b = new B();

          所以在一些 JVM 實(shí)現(xiàn)中,可能采取在使用時(shí)才會(huì)解析類(lèi)或接口中的符號(hào)引用,或采取在該類(lèi)或者接口被驗(yàn)證時(shí)一次性解析全部符號(hào)引用。這取決于采用的是哪種策略,也意味著解析過(guò)程可能在類(lèi)或者接口被初始化后還會(huì)進(jìn)行

          初始化

          在 Java 代碼中,如果想要初始化一個(gè)靜態(tài)字段,可以在聲明的時(shí)候直接賦值,也可以選擇在靜態(tài)代碼塊中對(duì)它賦值

          如果直接賦值的靜態(tài)字段被 final 修飾了,而且這個(gè)靜態(tài)字段是基本類(lèi)型或者字符串時(shí),就會(huì)被 Java 編譯器標(biāo)記成常量值,初始化就直接被 JVM 完成了。除此之外的直接賦值操作,還有所有靜態(tài)代碼塊中的代碼,就會(huì)被 Java 編譯器放到同一個(gè)方法中,并且把它命名為?

          類(lèi)加載的最后一步就是初始化,就是給標(biāo)記為常量值的字段賦值,執(zhí)行??方法的過(guò)程。這個(gè)時(shí)候 JVM 會(huì)通過(guò)加鎖來(lái)確保類(lèi)的??方法只被執(zhí)行一次

          ?方法可厲害了,因?yàn)椋?/p>

          • ?方法與類(lèi)的構(gòu)造函數(shù)不同,它不需要顯示的調(diào)用父類(lèi)的??方法,虛擬機(jī)會(huì)保證在子類(lèi)的??方法執(zhí)行之前,父類(lèi)的??方法已經(jīng)執(zhí)行完畢。因此在虛擬機(jī)中第一個(gè)被執(zhí)行的??方法的類(lèi)肯定是 java.lang.Object

          • ?方法對(duì)于類(lèi)或接口來(lái)說(shuō)不是必需的,如果一個(gè)類(lèi)中沒(méi)有靜態(tài)語(yǔ)句塊,也沒(méi)有對(duì)變量的賦值操作,那么編譯器可以不為這個(gè)類(lèi)生成??方法。

          • 接口中不能使用靜態(tài)初始化塊,但是仍有 static 變量的賦值操作,所以也會(huì)有??方法,但是接口執(zhí)行??方法不需要先執(zhí)行父接口的??方法。只有當(dāng)父接口中定義的變量被使用到時(shí),才會(huì)執(zhí)行??方法。

          • 虛擬機(jī)會(huì)保證一個(gè)類(lèi)的??方法在多線程環(huán)境中被正確的加鎖、同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類(lèi),那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類(lèi)的??方法,其它線程都需要阻塞等待

          ?方法執(zhí)行之后, JVM 才算成功的加載了 Java 類(lèi)

          類(lèi)的初始化何時(shí)會(huì)被觸發(fā)?

          那么,類(lèi)的初始化什么時(shí)候會(huì)被觸發(fā)呢?

          JVM 規(guī)范列舉了以下幾種觸發(fā)情況:

          1 , 當(dāng)虛擬機(jī)啟動(dòng)時(shí),初始化用戶(hù)指定的主類(lèi);

          2 ,當(dāng)遇到用以新建目標(biāo)類(lèi)實(shí)例的 new 指令時(shí),初始化 new 指令的目標(biāo)類(lèi);

          3 ,當(dāng)遇到調(diào)用靜態(tài)方法的指令時(shí),初始化該靜態(tài)方法所在的類(lèi);

          4 ,當(dāng)遇到訪問(wèn)靜態(tài)字段的指令時(shí),初始化該靜態(tài)字段所在的類(lèi);

          5 ,子類(lèi)的初始化會(huì)觸發(fā)父類(lèi)的初始化;

          6 ,如果一個(gè)接口定義了 default 方法,那么直接實(shí)現(xiàn)或者間接實(shí)現(xiàn)該接口的類(lèi)的初始化,會(huì)觸發(fā)該接口的初始化;

          7 ,使用反射 API 對(duì)某個(gè)類(lèi)進(jìn)行反射調(diào)用時(shí),初始化這個(gè)類(lèi);

          8 ,當(dāng)初次調(diào)用 MethodHandle 實(shí)例時(shí),初始化該 MethodHandle 指向的方法所在的類(lèi)

          再談 雙親委派機(jī)制

          在上面類(lèi)加載機(jī)制那塊,提了一下雙親委派機(jī)制

          我覺(jué)得之所以有這樣的機(jī)制,就是為了避免資源的浪費(fèi)。上面的雙親委派機(jī)制我們?cè)诂F(xiàn)實(shí)中也可以找到例子,比如說(shuō):公司部門(mén)有位程序員 A 發(fā)現(xiàn)如果做一個(gè)數(shù)據(jù)系統(tǒng)的話,來(lái)把公司各部門(mén)的數(shù)據(jù)打通,這樣就可以減少很多交流成本,那么他可能就會(huì)和老大去說(shuō),申請(qǐng)去做這個(gè)系統(tǒng),老大一看,這個(gè)方案完全可以抽成公共的呀,就自己去寫(xiě)了(父類(lèi)加載公共方法),也可能老大一看,你就自己去寫(xiě)吧(父類(lèi)不加載時(shí),子類(lèi)再進(jìn)行加載),更巧的是,程序員 B 也發(fā)現(xiàn)了,他也去找老大說(shuō),這個(gè)時(shí)候老大會(huì)說(shuō)什么呢?這個(gè)事情 A 去做了,就不用太擔(dān)心了

          那如果程序員 A 和 B 發(fā)現(xiàn)了之后沒(méi)有和老大交流,都自己悶頭去做了,這樣的話,同樣的系統(tǒng)做了兩遍,還浪費(fèi)了兩個(gè)人的時(shí)間精力,由此造成的資源浪費(fèi)太大了

          我覺(jué)得雙親委派的機(jī)制類(lèi)似于這樣,因?yàn)檫@個(gè)機(jī)制的存在,讓資源浪費(fèi)的現(xiàn)象大大減少了

          但是 tomcat 打破了這種機(jī)制,這怎么說(shuō)?

          我們都知道 tomcat 是個(gè) web 容器,那么它應(yīng)該:

          • 支持部署兩個(gè)應(yīng)用程序,不同的應(yīng)用程序可能會(huì)依賴(lài)同一個(gè)第三方類(lèi)庫(kù)的不同版本,就比如兩個(gè)應(yīng)用程序,其中一個(gè)依賴(lài)的是一個(gè)類(lèi)庫(kù)的 v1.0 ,另外一個(gè)依賴(lài)的是同樣一個(gè)類(lèi)庫(kù)的 v2.0 ,那么 tomcat 是不是應(yīng)該允許這個(gè)類(lèi)庫(kù)的 1.0 和 2.0 版本都存在?

          • 部署在同一個(gè) web 容器中相同的類(lèi)庫(kù)相同的版本是應(yīng)該可以共享的。就比如,服務(wù)器上有 100 個(gè)應(yīng)用程序,這些程序依賴(lài)的都是相同的類(lèi)庫(kù),那 tomcat 總不能把這 100 份相同的類(lèi)庫(kù)都加載到虛擬機(jī)里面去吧,要是非要加載進(jìn)去,那服務(wù)器不得分分鐘炸了

          • web 容器需要支持 jsp 文件的修改,也就是說(shuō),當(dāng)程序運(yùn)行之后,我對(duì) jsp 文件進(jìn)行了修改,那么 tomcat 是不是也應(yīng)該支持?如果不支持的話,那我修改一次就不能用了,不合適吧?

          基于上面三點(diǎn),就能看到 tomcat 其實(shí)是打破了雙親委派機(jī)制的

          比如第一個(gè)問(wèn)題,第三方類(lèi)庫(kù)就是同樣一個(gè)資源,在雙親委派機(jī)制中,同樣一個(gè)資源是不應(yīng)該加載兩次的,但是在 tomcat 里面卻被允許了;但是第二個(gè)問(wèn)題好像又在說(shuō)雙親委派的機(jī)制,正是因?yàn)殡p親委派機(jī)制的存在,所以第二個(gè)問(wèn)題就不是問(wèn)題了嘛;第三個(gè)問(wèn)題又打破了雙親委派機(jī)制,因?yàn)槿绻淮蚱频脑挘瓉?lái)的 jsp 文件已經(jīng)加載進(jìn)來(lái)了,現(xiàn)在對(duì)它進(jìn)行了修改,那么應(yīng)該還會(huì)加載原來(lái)的 jsp 文件,這樣的話修改豈不是無(wú)效了?

          所以, tomcat 打破了雙親委派機(jī)制,但并不是完全打破

          至于 tomcat 打破雙親委派的機(jī)制,阿粉還沒(méi)搞懂,等阿粉搞懂了再來(lái)寫(xiě)吧

          或者你搞懂了嘛?給阿粉講講好不好~

          參考:極客時(shí)間 – 深入拆解 Java 虛擬機(jī)

          End

          如果大家喜歡我們的文章,歡迎大家轉(zhuǎn)發(fā),點(diǎn)擊在看讓更多的人看到。



          43063048c2a8f597b46786d1d6b37b6c.webp

          0、十月豐收季,看看我都收獲了什么?

          50dc8c0c5110fdb4afba4351e03590a1.webp

          瀏覽 42
          點(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>
                  日本A级天堂 | 日本午夜福利中文字幕 | 8x8x皇冠视频免费观看 | 成人性爱视频在线 | 91aaa国产 |