1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        JVM 是如何加載 Java 類的?

        共 3636字,需瀏覽 8分鐘

         ·

        2020-11-27 12:02



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

        如何加載?不就是 加載,鏈接,初始化 這三步嘛,說白了不就是類加載過程么

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

        加載

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

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

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

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

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

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

        鏈接

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

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

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

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

        鏈接這塊又分為三部分:驗(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)造其他跟類層次相關(guān)的數(shù)據(jù)結(jié)構(gòu),比如說用來實(shí)現(xiàn)虛方法的動(dòng)態(tài)綁定的方法表,這個(gè)方法表是用來解決動(dòng)態(tài)綁定的問題的,解析時(shí)通過這個(gè)方法表,根據(jù)實(shí)際類型來解析獲取對(duì)應(yīng)的方法。

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

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

        在解析階段,不同的 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 沒有被使用,所以暫時(shí)不加載 B。在真正使用 B 的時(shí)候才進(jìn)行加載,比如?b = new B();

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

        初始化

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        End

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



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

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            中文字幕理伦片免费看 | 天天日天操 | 中文字幕精品视频在线 | 成人精品视频在线观看第十八区 | 熟女性爱视频网站 | 亚洲人东京热123 | 日韩激情视频网站 | 高清美穴 | 日韩手机在线视频 | 三上悠亚在线资源 |