是的!又一篇Java類加載介紹塑荒!

類加載基礎概念

嘗試用5W1H模型來聊聊Java的類加載熄赡。

什么是類加載? 簡單的說齿税,把字節(jié)碼加載到JVM中的過程彼硫,我們就稱之為類加載。輸入是某個類的.class文件的字節(jié)流凌箕,輸出是JVM所管理的方法區(qū)中關于該類的信息拧篮。

為什么要有類加載? 我的理解是為了更好的支持動態(tài)特性牵舱,比如說熱部署串绩,就是利用了JVM可以動態(tài)加載字節(jié)碼的機制實現(xiàn)的。

什么時候進行類加載芜壁? 總的來說礁凡,JVM需要某個類的信息,而又沒有的時候慧妄,就會觸發(fā)類加載顷牌。具體來說分了以下幾個場景:

  1. 遇到new、getstatic塞淹、putstatic韧掩、invokestatic等指令時,如果類還沒有加載過就會觸發(fā)類加載窖铡;
  2. 子類進行類加載時疗锐,如果父類還沒有加載過,會先觸發(fā)父類的加載费彼;
  3. 使用反射進行各種操作時滑臊,如果類還沒有加載過,會先進行類加載箍铲。
  4. 虛擬機啟動時雇卷,會首先加載含有main方法的類。
  5. 其他情況,這里不是抄書关划,所以我們先不再枚舉小染。

誰來負責類加載? 類加載有專門的類加載器來完成贮折,類加載器又有等級森嚴的層級關系裤翩,爺爺輩的類加載器叫啟動類加載器,然后是爸爸輩调榄,叫拓展類加載器踊赠,最后是應用程序類加載器。這里涉及到一個類加載過程中各個類加載器是如何分工合作的每庆,會在雙親委派模型中提到筐带。

怎樣進行類加載? 前面提到過類加載就是把類的字節(jié)碼塞進虛擬機的過程缤灵,那么具體怎么做呢伦籍?

首先,類加載器需要從某處獲得字節(jié)碼的二進制字節(jié)流腮出。為什么不說字節(jié)碼文件鸽斟?因為除了從.class文件中獲取,還可以從壓縮包中解壓獲取利诺,從網(wǎng)絡中獲雀恍睢(比如Applet),甚至是動態(tài)生成一個(想想動態(tài)代理)都是可以的慢逾。這個動作立倍,我們稱為加載。(TO-DO 這個時候生成Class對象了嗎侣滩?

接著口注,這個對象還不能直接使用,我們需要把針對它做各種校驗君珠,比如字節(jié)碼本身是否合規(guī)寝志,是否是該版本的虛擬機支持,如果都通過了策添,就需要給靜態(tài)變量開辟一塊內(nèi)存區(qū)域材部,然后賦零值,這里的零值指的是唯竹,當內(nèi)存中沒有數(shù)據(jù)時乐导,變量的值,比如對于int型來說零值是0浸颓,對于boolean型來說物臂,零值是false旺拉。最后,如果這個類中存在符號引用棵磷,還需要把符號引用解析為具體的內(nèi)存地址蛾狗。以上所有的動作,我們合并起來仪媒,稱之為鏈接沉桌。

最后,終于到了給靜態(tài)字段賦值的時候了规丽,無論是直接賦值還是通過靜態(tài)塊來完成蒲牧,編譯器都會把這些賦值語句收集到一起撇贺,并且按程序書寫的順序赌莺,然后放在一個叫clinit的方法中,依次執(zhí)行松嘶。這個動作艘狭,我們稱為初始化

類加載進階

以上是針對類加載機制的一個簡單介紹翠订,下面我們進行一些更加高階的講解巢音。

一種優(yōu)雅的單例實現(xiàn)

實現(xiàn)單例有多種不同的寫法,也個有優(yōu)缺點尽超,其中“餓漢式”的寫法最為簡潔官撼,但缺點是一旦觸發(fā)了類加載就會同步進行實例化。觸發(fā)的機制前面的基礎篇中已經(jīng)提到過似谁,比如調(diào)用Resource中存在的任意一個static字段或者方法傲绣,就會觸發(fā)Resource類的類加載。

而下面的寫法通過引入靜態(tài)內(nèi)部類完美的解決了這個問題巩踏。結(jié)合剛才提到的類加載機制秃诵,說說這是為什么?

public class Resource {

    private Resource() {}

    public static Resource getInstance() {
        return Holder.resource;
    }

    private static class Holder {
        public static final Resource resource = new Resource();
    }
}

關于雙親委派模型

剛才介紹了幾個不同的類加載器塞琼,那么他們之間是怎樣合作的菠净?我們結(jié)合ClassLoader類中的loadClass方法來看:

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException {

  synchronized (getClassLoadingLock(name)) {

    // 首先,檢查該類是否已經(jīng)被加載過
    Class<?> c = findLoadedClass(name);
    if (c == null) {

      // 針對未加載過的類彪杉,先嘗試讓父類加載器進行加載
      try {
        // 啟動類加載器是通過C++實現(xiàn)的毅往,只能表示為null
        // 因此這里有2個邏輯分支
        if (parent != null) {
          c = parent.loadClass(name, false);
        } else {
          // 返回一個啟動類加載器加載的類,如果沒有則返回null
          c = findBootstrapClassOrNull(name);
        }
      } catch (ClassNotFoundException e) {
        
      }

      // 如果父類加載器無法加載派近,再嘗試自己加載
      if (c == null) {
        c = findClass(name);
      }
    }

    // 其他實現(xiàn)細節(jié)...

    return c;
  }
}

通過自定義類加載器實現(xiàn)熱部署

熱部署指的是不需要重啟應用就可以動態(tài)的替換掉其中的一些功能煞抬,類加載器給我們提供了這樣一種實現(xiàn)的思路。

首先构哺,我們說在Java的世界里革答,通過一個類的全限定名 + 類加載器战坤,可以唯一的定位一個類,也就是說残拐,哪怕是同一個.class文件途茫,通過不同的類加載器進入JVM,它們之間也是互相隔離的溪食。

基于上述事實囊卜,當我們希望只是升級某個類的功能時,就可以通過這樣的機制來實現(xiàn):為該類實例化一個新的類加載器错沃,并重新加載該類栅组,最后替換掉之前舊的版本。

根據(jù)這樣的思路枢析,我們可以定義一個MyTest類玉掸,擁有一個showVersion()方法,在第一個版本中會打印1.0醒叁,在第二個版本中會打印2.0司浪,代表功能進行了升級。

public class MyTest {
  public void showVersion() {
    System.out.println("1.0版本");
  }
}

接著把沼,需要自定義一個類加載器啊易,重寫部分方法,簡單來說饮睬,它會根據(jù)類的全限定名租谈,在/tmp目錄下找對應的字節(jié)碼文件,針對特定的類捆愁,如MyTest割去,不經(jīng)過雙親委派模型,直接加載進內(nèi)存中牙瓢。

public class MyClassLoader extends ClassLoader {

  // 指定那些類可以通過自定義類加載器的方式加載
  private Set<String> classNamesLoadMyself = new HashSet<>();

  public MyClassLoader(String ... classNames) {
    for (String className : classNames) {
      classNamesLoadMyself.add(className);
    }
  }

  @Override
  protected Class<?> findClass(String name) {
    // 根據(jù)路徑和類名找到對應的文件并轉(zhuǎn)化為相應的字節(jié)流
    byte[] bytes = FileUtil.getClassByte("/tmp", name);
    return defineClass(name, bytes, 0, bytes.length);
  }

  @Override
  public Class<?> loadClass(String name) throws ClassNotFoundException {
    // 如果是指定了要自定義類加載的類劫拗,則繞開雙親委派模型
    if (classNamesLoadMyself.contains(name)) {
      return findClass(name);
    }
    return super.loadClass(name);
  }
}

最后,我們對這個自定義的類加載器做一個測試矾克。

public class MyClassLoaderClient {

  public static void main(String[] args) throws Exception {
    for (int i = 0; i < 10; i++) {
        // 實例化一個類加載器
        MyClassLoader myClassLoader = new MyClassLoader("MyTest");

        // 注意這里不能直接強制類型轉(zhuǎn)化為MyTest
        Object myTest = myClassLoader.loadClass(className).newInstance();
        myTest.getClass().getMethod("showVersion").invoke(myTest);

        // 休眠1秒
        TimeUnit.SECONDS.sleep(1);
    }
  }
}

在這個測試類中有一行注釋页慷,不能將實例化的MyTest做強制類型轉(zhuǎn)換,請問這是為什么呢胁附?

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酒繁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子控妻,更是在濱河造成了極大的恐慌州袒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弓候,死亡現(xiàn)場離奇詭異郎哭,居然都是意外死亡他匪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門夸研,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邦蜜,“玉大人,你說我怎么就攤上這事亥至〉可颍” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵姐扮,是天一觀的道長絮供。 經(jīng)常有香客問我,道長茶敏,這世上最難降的妖魔是什么壤靶? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮睡榆,結(jié)果婚禮上萍肆,老公的妹妹穿的比我還像新娘袍榆。我一直安慰自己胀屿,他們只是感情好,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布包雀。 她就那樣靜靜地躺著宿崭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪才写。 梳的紋絲不亂的頭發(fā)上葡兑,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機與錄音赞草,去河邊找鬼讹堤。 笑死,一個胖子當著我的面吹牛厨疙,可吹牛的內(nèi)容都是我干的洲守。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沾凄,長吁一口氣:“原來是場噩夢啊……” “哼梗醇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起撒蟀,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤叙谨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后保屯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體手负,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡涤垫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了竟终。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雹姊。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖衡楞,靈堂內(nèi)的尸體忽然破棺而出吱雏,到底是詐尸還是另有隱情,我是刑警寧澤瘾境,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布歧杏,位于F島的核電站,受9級特大地震影響迷守,放射性物質(zhì)發(fā)生泄漏犬绒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一兑凿、第九天 我趴在偏房一處隱蔽的房頂上張望凯力。 院中可真熱鬧,春花似錦礼华、人聲如沸咐鹤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祈惶。三九已至,卻和暖如春扮匠,著一層夾襖步出監(jiān)牢的瞬間捧请,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工棒搜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疹蛉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓力麸,卻偏偏與公主長得像可款,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子末盔,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容