<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>

          一個(gè) Java 類的加載

          共 5789字,需瀏覽 12分鐘

           ·

          2021-06-03 22:50

          該系列文章,主要是為了深入學(xué)習(xí)Java完成的一條鏈,推薦閱讀的整體順序?yàn)椋?/span>Java的內(nèi)存模型(根源),一個(gè)java文件被執(zhí)行的歷程,一個(gè)Java類的加載,Java的垃圾回收機(jī)制及算法,Linux(六):系統(tǒng)運(yùn)維常用命令  和 Java程序運(yùn)行狀態(tài)的監(jiān)控(實(shí)用,定位Java程序問題)


          0x01:類加載

          我一直認(rèn)為,不應(yīng)該把類的加載,單獨(dú)當(dāng)作一個(gè)模塊去看,那樣就是單純地去看一個(gè)知識(shí)點(diǎn),不利于建立Java全體系的知識(shí)架構(gòu),更別說實(shí)際應(yīng)用到開發(fā)中(閱讀優(yōu)秀開源項(xiàng)目、寫出高質(zhì)量的代碼或定位問題)。所以這里應(yīng)該串聯(lián)一整個(gè)Java語言編譯的全流程。

          下面說一下在Java中類加載的概念及它在整個(gè)Java程序得以運(yùn)行的過程中所處的位置:

          類的加載指的是將類的字節(jié)碼文件(.class文件)中數(shù)據(jù)讀入到內(nèi)存中,將其放在運(yùn)行時(shí)數(shù)據(jù)區(qū)的方法區(qū)內(nèi),然后在堆區(qū)創(chuàng)建一個(gè)java.lang.Class對象(關(guān)于這部分可以看之前的一篇關(guān)于Java反射的內(nèi)容:入口),用來封裝類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)。類的加載的最終產(chǎn)品是位于堆區(qū)中的Class對象,Class對象封裝了類在方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu),并且向Java程序員提供了訪問方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口。

          類加載器并不需要等到某個(gè)類被“首次主動(dòng)使用”時(shí)再加載它,JVM規(guī)范允許類加載器在預(yù)料某個(gè)類將要被使用時(shí)就預(yù)先加載它,如果在預(yù)先加載的過程中遇到了.class文件缺失或存在錯(cuò)誤,類加載器必須在程序首次主動(dòng)使用該類時(shí)才報(bào)告錯(cuò)誤(LinkageError錯(cuò)誤)如果這個(gè)類一直沒有被程序主動(dòng)使用,那么類加載器就不會(huì)報(bào)告錯(cuò)誤。

          上面的話感覺很懵?沒事,我給你翻譯翻譯,和那些編譯時(shí)需要進(jìn)行連接工作的語言不同(那些語言都是完成全部代碼的編譯連接全部放到內(nèi)存中才開始運(yùn)行),在Java里,類的加載、連接和初始化過程都是在程序運(yùn)行起來以后進(jìn)行的,或者說是在運(yùn)行期間完成的(懵逼?沒事,先保留困惑,詳細(xì)的解釋會(huì)在后面類的加載時(shí)機(jī)那塊做出解釋)。它的這種設(shè)計(jì),會(huì)在類加載時(shí)增加一定的性能開銷,但是這樣是為了滿足Java的高度靈活性,Java是天生地可以動(dòng)態(tài)擴(kuò)展地語言,這一特性就是依賴運(yùn)行期動(dòng)態(tài)加載和動(dòng)態(tài)連接實(shí)現(xiàn)的。


          0x02:類的生命周期

          說類加載的過程之前,我們先來了解一下,類的整個(gè)生命周期要經(jīng)歷什么

          類從被加載到虛擬機(jī)的內(nèi)存中開始,到卸載出內(nèi)存(整個(gè)程序\系統(tǒng)運(yùn)行結(jié)束虛擬機(jī)關(guān)閉)為止,它的整個(gè)生命周期包括:加載、鏈接、初始化、使用、卸載。

          因?yàn)檫@里著重說類的加載這一過程,所以類的使用和卸載就不介紹了,后面就默認(rèn)類的加載這個(gè)過程包含:加載、鏈接、初始化

          • 加載(Load

          這里叫做加載,很容易讓人誤會(huì),會(huì)覺得類的加載就是指這里,其實(shí)不是這個(gè)樣子,這里的加載二字和類的加載不是一回事,可以這么理解,加載是類加載過程的一個(gè)階段,這一階段,虛擬機(jī)主要是做三件事:

          1、根據(jù)類的全路徑獲取類的二進(jìn)制字節(jié)流

          2、將這個(gè)字節(jié)流對應(yīng)的結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)(把編碼的組織方式變成虛擬機(jī)運(yùn)行時(shí)所能解讀的結(jié)構(gòu),存放于方法區(qū))

          3、在內(nèi)存中生成一個(gè)Class對象(java.lang.Class),由這個(gè)對象來關(guān)聯(lián)方法區(qū)中的數(shù)據(jù)

          這里特別注意一下,以上的三點(diǎn),只是虛擬機(jī)規(guī)范定義的,至于具體如何實(shí)現(xiàn),是依賴具體的虛擬機(jī)來的;例如,第一件事的獲取二進(jìn)制流,并不一定是從字節(jié)碼文件(Class文件)從進(jìn)行獲取,它可以是從ZIP中獲取,從網(wǎng)絡(luò)中獲取,利用代理在計(jì)算過程中生成等等;

          還有第三件事中生成的Class對象,也并不一定是在堆區(qū)的,例如HostSpot虛擬機(jī)的實(shí)現(xiàn)上,Class對象就是放在方法區(qū)的。

          • 鏈接(Link)

            鏈接階段又細(xì)分為驗(yàn)證、準(zhǔn)備、解析三個(gè)步驟:

          驗(yàn)證

          作為鏈接的第一步,它的職責(zé)就是確保Class文件的字節(jié)流中包含的信息是符合規(guī)定的,并且不會(huì)對虛擬機(jī)進(jìn)行破壞;其實(shí)說白了就是它主要責(zé)任就是保證你寫的代碼是符合Java語法的,是合理可行的。如果不合理,編譯器是拒絕的。驗(yàn)證主要是針對 文件格式的驗(yàn)證、元數(shù)據(jù)的驗(yàn)證,字節(jié)碼的驗(yàn)證,符號(hào)引用的驗(yàn)證;

          文件格式的驗(yàn)證是對字節(jié)流進(jìn)行是否符合Class文件格式的驗(yàn)證,元數(shù)據(jù)的驗(yàn)證主要是語義語法的驗(yàn)證,即驗(yàn)是否符合Java語言規(guī)范,例如:一個(gè)類是否有父類(我們知道Java中處理Object,所有的類都應(yīng)該有個(gè)父類),字節(jié)碼的驗(yàn)證主要是對數(shù)據(jù)流和控制流進(jìn)行驗(yàn)證,確保程序語義是合法、合邏輯的,例如:在操作棧先放了一個(gè)Int型的數(shù)據(jù),后面某個(gè)地方使用的時(shí)候卻用Long型來接它。符號(hào)引用的驗(yàn)證是確保解析動(dòng)作能夠正常執(zhí)行。

          整個(gè)驗(yàn)證過程,保證了Java語言的安全性,不會(huì)出現(xiàn)不可控的情況。(這里補(bǔ)充一下,這里說的驗(yàn)證、不可控,包括上面舉的例子,并不是我們編程中寫的類似于a != null這種,它是在我們編寫的程序更下一層的字節(jié)碼的解析上來說的),對于加載的過程來說,驗(yàn)證階段很重要,但并不一定是必須的,因?yàn)樗鼘Τ绦蜻\(yùn)行期并沒有影響,僅僅旨在保證語言的安全性,如果所運(yùn)行的全部代碼都已經(jīng)被反復(fù)使用和驗(yàn)證過,那么在實(shí)施階段,可以考慮使用-Xverify:none參數(shù)來關(guān)閉大部分的驗(yàn)證過程,以達(dá)到縮短虛擬機(jī)加載的時(shí)間。

          準(zhǔn)備

          準(zhǔn)備階段主要作用是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,即這些變量所使用的內(nèi)存,都在方法區(qū)中進(jìn)行分配。這里需要注意,這時(shí)候進(jìn)行內(nèi)存分配的僅僅是類變量,換句話說也就是靜態(tài)變量(static修飾的),并不包括實(shí)例變量,實(shí)例變量會(huì)在實(shí)例化時(shí)分配在堆內(nèi)存中。初始值也并不是我們的賦值,

          例如:

          public Class A{

            public String name;

            public static int value = 987;

          }

          就像剛剛講的,這里在準(zhǔn)備階段,只會(huì)對value變量進(jìn)行內(nèi)存分配,并不會(huì)對name進(jìn)行分配,其次,在準(zhǔn)備階段,對value分配完內(nèi)存,會(huì)同時(shí)賦予初始值,但是并不會(huì)賦給它987,在準(zhǔn)備階段,value的值是0。而賦值為987的指令,是在程序被編譯后,存放于類構(gòu)造器<clinit>()方法中,所以把value賦值987的操作,會(huì)在初始化階段才會(huì)進(jìn)行。(這里補(bǔ)充個(gè)特殊情況,如果我們寫成 public static final int  value = 987,那么變量value 在準(zhǔn)備階段就會(huì)被賦值為987,這就是為什么很多書在講final字段的時(shí)候說它一般用來定義常量,且一經(jīng)使用,就不可以被更改的原因)

          解析

          解析階段的任務(wù)是將常量池中的符號(hào)引用替換為直接引用

          常量池可以理解為存放我們代碼符號(hào)的地方,例如我們代碼中聲明的變量,它僅僅是個(gè)符號(hào),并不具備實(shí)際內(nèi)存,所有這些符號(hào),都會(huì)放在常量池中。例如,一個(gè)類的方法為test(),則符號(hào)引用即為test,這個(gè)方法存在于內(nèi)存中的地址假設(shè)為0x123456,則這個(gè)地址則為直接引用。

          符號(hào)引用:

          符號(hào)引用更多的是以一組符號(hào)來描述所引用的內(nèi)存目標(biāo),符號(hào)和內(nèi)存空間實(shí)際并沒有關(guān)系,引用的目標(biāo)也不一定在內(nèi)存里,只是我們在代碼中自己寫的時(shí)候區(qū)分的,例如一句 Persion one;其中one就是個(gè)’o‘,’n‘,’e‘三個(gè)符號(hào)的組合,它啥也不是。

          直接引用:

          直接引用可以是直接指向內(nèi)存空間的指針、相對便宜量或是一個(gè)能夠簡潔定位到內(nèi)存目標(biāo)的句柄。

          解析動(dòng)作主要是針對 類、接口、字段、類方法、方法類型、方法句柄和調(diào)用點(diǎn)限定符號(hào)的引用進(jìn)行。

          初始化(Initialize)

          在類的加載過程中,加載、連接完全由虛擬機(jī)來主導(dǎo)和控制,到了初始化這一階段,才是真正開始執(zhí)行類中定義的Java代碼。初始化其實(shí)我個(gè)人理解的就是該階段是為類的類變量初始化值的,在準(zhǔn)備階段變量已經(jīng)進(jìn)了一次賦值,只不過那是系統(tǒng)要求的初始值,而在初始化階段的賦值,則是根據(jù)研發(fā)人員編寫的主觀程序去初始化變量和其他資源。在初始化這步,進(jìn)行賦值的方式有兩種:

          1、在聲明類變量時(shí),直接給變量賦值

          2、在靜態(tài)初始化塊為類變量賦值

          使用

          就是對象之間的調(diào)用通信等等

          卸載(死亡)

          遇到如下幾種情況,即類結(jié)束生命周期:

          • 執(zhí)行了System.exit()方法

          • 程序正常執(zhí)行結(jié)束

          • 程序在執(zhí)行過程中遇到了異?;蝈e(cuò)誤而異常終止

          • 由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致Java虛擬機(jī)進(jìn)程終止


          0x03:類加載器

          之前說了那么多一個(gè)類的聲明周期,更多的是一種理論基礎(chǔ),映射到具體的代碼層面,到底是什么來完成類加載這個(gè)過程的就是這里要說的——類加載器。

          虛擬機(jī)在設(shè)計(jì)時(shí),把類加載階段的 “通過一個(gè)類的全路徑名來獲取該類字節(jié)碼二進(jìn)制流” 這個(gè)動(dòng)作放到了 Java虛擬機(jī)之外去完成,而負(fù)責(zé)實(shí)現(xiàn)這個(gè)動(dòng)作的模塊就叫做類加載器。

          類加載器分類

          啟動(dòng)類加載器

          1、它用來加載 Java 的核心庫(JAVA_HOME/jre/lib/rt.jar,sun.boot.class.path路徑下的內(nèi)容),并不是Java代碼完成,而是用原生代碼(C語言或C++)來實(shí)現(xiàn)的,并不繼承自 java.lang.ClassLoader。

          2、加載擴(kuò)展類和應(yīng)用程序類加載器。并指定他們的父類加載器。

          擴(kuò)展類加載器

          這一類加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),用來加載 Java 的擴(kuò)展庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內(nèi)容) 。Java 虛擬機(jī)的實(shí)現(xiàn)會(huì)提供一個(gè)擴(kuò)展庫目錄。該類加載器在此目錄里面查找并加載 Java類。

          應(yīng)用類加載器

          這個(gè)類加載器由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn),由于這個(gè)類加載器是ClassLoader中g(shù)etSystemClassLoader()方法的返回值,所以它也成為系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑下所指定的類庫,開發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器,一般情況下這個(gè)就是系統(tǒng)默認(rèn)的類加載器。

          自定義加載器

          開發(fā)人員可以通過繼承 java.lang.ClassLoader類的方式實(shí)現(xiàn)自己的類加載器,以滿足一些特殊的需求。

          類加載的代理(雙親委派模式)

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

          例如類java.lang.Object,它存放在rt.jart之中,無論哪一個(gè)類加載器都要加載這個(gè)類.最終都是雙親委派模型最頂端的Bootstrap類加載器去加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反,如果沒有使用雙親委派模型.由各個(gè)類加載器自行去加載的話,如果用戶編寫了一個(gè)稱為“java.lang.Object”的類,并存放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類.java類型體系中最基礎(chǔ)的行為也就無法保證。應(yīng)用程序也將會(huì)一片混亂。

          當(dāng)然也并不是所有的加載機(jī)制都是雙親委派的方式,例如tomcat作為一個(gè)web服務(wù)器,它本身實(shí)現(xiàn)了類加載,該類加載器也使用代理模式(不同于前面說的雙親委托機(jī)制),所不同的是它是首先嘗試去加載某個(gè)類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。但也是為了保證安全,這樣核心庫就不在查詢范圍之內(nèi)。

          類的加載時(shí)機(jī)

          最后說一個(gè)比較重要也是諸多困惑的地方,就是什么時(shí)候才會(huì)加載類。

          加載、驗(yàn)證、準(zhǔn)備、初始化、卸載這五個(gè)步驟是確定的,類的加載過程必須按部就班地開始,但是解析階段就不一定了,它在某些情況下是可以在初始化階段之后再開始,看到這里,肯定滿腦子????,其實(shí)不必驚訝,我一開始就說了,它這是為了滿足Java語言地動(dòng)態(tài)時(shí)綁定(泛型、多態(tài)的本質(zhì))這個(gè)特性來的,它是按部就班的開始,而不是按部就班的 “進(jìn)行”或者“結(jié)束”,這些階段其實(shí)是相互交叉混合進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過程中調(diào)用、激活另外一個(gè)階段。

          其實(shí)上面的話有些繞,我們從類的使用上來看這個(gè)問題,類的使用分為主動(dòng)引用和被動(dòng)引用:

          1、主動(dòng)引用類(肯定會(huì)初始化)

          • new一個(gè)類的對象。

          • 調(diào)用類的靜態(tài)成員(除了final常量)和靜態(tài)方法。

          • 使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用。

          • 當(dāng)虛擬機(jī)啟動(dòng),java Hello,則一定會(huì)初始化Hello類。說白了就是先啟動(dòng)main方法所在的類。

          • 當(dāng)初始化一個(gè)類,如果其父類沒有被初始化,則先會(huì)初始化他的父類

          被動(dòng)引用

          • 當(dāng)訪問一個(gè)靜態(tài)域時(shí),只有真正聲明這個(gè)域的類才會(huì)被初始化。例如:通過子類引用父類的靜態(tài)變量,不會(huì)導(dǎo)致子類初始化。

          • 通過數(shù)組定義類引用,不會(huì)觸發(fā)此類的初始化。

          • 引用常量不會(huì)觸發(fā)此類的初始化(常量在編譯階段就存入調(diào)用類的常量池中了)。

          首先,Java的編譯不是像其他語言一樣,都加載到內(nèi)存中才開始運(yùn)行,而且動(dòng)態(tài)的,也就會(huì)出現(xiàn):先運(yùn)行了一部分,初始化了一些類,但是在這一部分運(yùn)行的代碼里被動(dòng)引用了未被初始化的類(例如static變量),這時(shí)候就會(huì)出現(xiàn)了這種違背順序的情況。總的來說就是,

          • 先加載并連接當(dāng)前類

          • 父類沒有被加載,則去加載、連接、初始化父類,依舊是先加載并連接,然后再判斷有無父類,如此循環(huán)(所以JVM先將Object加載)

          • 如果類中有初始化語句,包括聲明時(shí)賦值與靜態(tài)初始化塊,則按順序進(jìn)行初始化


          source:https://www.cnblogs.com/TheGCC/p/14738123.html

          最近給大家找了  百萬級電商


          資源,怎么領(lǐng)???


          掃二維碼,加我微信,回復(fù):百萬級電商

           注意,不要亂回復(fù) 

          沒錯(cuò),不是機(jī)器人
          記得一定要等待,等待才有好東西


          瀏覽 95
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  成人无码777 | 日韩无码小电影 | 国产人人操 | 天天操天天日天天操 | 爱爱大全在线 |