Java ClassLoader 和熱加載

  • 熱加載:對(duì)jvm方法區(qū)中類定義進(jìn)行替換问裕,因?yàn)槎?heap)中的Class對(duì)象是對(duì)方法區(qū)對(duì)象的封裝,所以可以理解為對(duì)Class對(duì)象的替換,
    當(dāng)一個(gè)class被替換后烁登,系統(tǒng)無(wú)需重啟,替換的類會(huì)立即生效蔚舀。


    類加載
  • 類的加載過(guò)程

    • 裝載:查找并加載類的二進(jìn)制數(shù)據(jù)饵沧;
    • 鏈接:
      • 驗(yàn)證:確保被加載類的正確性;

        • 驗(yàn)證階段是鏈接階段的第一步赌躺,目的就是確保class文件的字節(jié)流中包含的信息符合虛擬機(jī)的要求狼牺,不能危害虛擬機(jī)自身安全。驗(yàn)證階段主要包括四個(gè)檢驗(yàn)過(guò)程:文件格式驗(yàn)證礼患、元數(shù)據(jù)驗(yàn)證是钥、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。
        • 文件格式驗(yàn)證
          驗(yàn)證class文件格式規(guī)范
        • 元數(shù)據(jù)驗(yàn)證
          就是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析讶泰,保證描述的信息符合java語(yǔ)言規(guī)范咏瑟。驗(yàn)證點(diǎn)可能包括(這個(gè)類是否有父類(除Object)、這個(gè)類是否繼承了不允許被繼承的類(final修飾的)痪署、如果這個(gè)類的父類是抽象類码泞,是否實(shí)現(xiàn)了父類或接口中要求實(shí)現(xiàn)的方法)。
        • 字節(jié)碼驗(yàn)證
          進(jìn)行數(shù)據(jù)流和控制流分析狼犯,這個(gè)階段對(duì)類的方法體進(jìn)行校驗(yàn)余寥,保證被校驗(yàn)的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)的行為领铐。
        • 符號(hào)引用驗(yàn)證
          符號(hào)引用中通過(guò)字符串描述的權(quán)限定名是否能找到對(duì)應(yīng)的類、符號(hào)引用類中的類宋舷,字段和方法的訪問(wèn)性(private protected public default)是否能被當(dāng)前類訪問(wèn)绪撵。
      • 準(zhǔn)備:為類的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值祝蝠;
        這個(gè)階段就是為類變量分配內(nèi)存并設(shè)置類變量初始值的階段音诈,這些內(nèi)存將在方法區(qū)中進(jìn)行分配。要注意的是绎狭,進(jìn)行分配內(nèi)存的只是包括類變量细溅,而不包括實(shí)例變量,實(shí)例變量是在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在java堆中的儡嘶。通常情況下喇聊,初始值為零值,假設(shè)public static int value=2;那么value在準(zhǔn)備階段過(guò)后的初始值為0蹦狂,不為2誓篱,這時(shí)候只是開(kāi)辟了內(nèi)存空間,并沒(méi)有運(yùn)行java代碼凯楔,value賦值為2的指令是程序被編譯后窜骄,存放于類構(gòu)造器<init>()方法之中,所以value被賦值為2是在初始化階段才會(huì)執(zhí)行摆屯。對(duì)于一些特殊情況啊研,如果類字段屬性表中存在ConstantValue屬性,那在準(zhǔn)備階段變量value就會(huì)被初始化為ContstantValue屬性所指的值鸥拧,那么對(duì)于上面value,編譯時(shí)javac將會(huì)為value生成ConstantValue屬性,在準(zhǔn)備階段虛擬機(jī)就會(huì)根據(jù)ConstantValue將value設(shè)置為2

      • 解析:解析階段是虛擬機(jī)常量池的符號(hào)引用替換為直接引用的過(guò)程削解。

        • 符號(hào)引用:符號(hào)引用是一組符號(hào)來(lái)描述所引用的對(duì)象富弦,符號(hào)可以是任何形式的字面量,只要使用時(shí)能定位到目標(biāo)即可氛驮,符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān)腕柜,引用的目標(biāo)對(duì)象并不一定已經(jīng)加載到內(nèi)存中。
        • 直接引用:直接引用可以是直接指向目標(biāo)對(duì)象的指針矫废、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄(一種特殊的智能指針)盏缤。直接引用是與虛擬機(jī)內(nèi)存布局實(shí)現(xiàn)相關(guān),同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同蓖扑,如果有了直接引用唉铜,那引用的目標(biāo)必定存在內(nèi)存中。
        • 解析過(guò)程主要針對(duì)類或接口律杠、字段潭流、類方法竞惋、接口方法四類符號(hào)引用進(jìn)行。
    • 初始化:為類的靜態(tài)變量賦予正確的初始值灰嫉;
      • 類的初始化階段是加載過(guò)程的最后一步拆宛,在準(zhǔn)備階段,類變量已賦過(guò)一次系統(tǒng)要求的初始值讼撒,而在初始化階段浑厚,則是根據(jù)程序員通過(guò)程序制定的主觀計(jì)劃去初始化類變量和其他資源,或者可以從另外一個(gè)角度表達(dá):初始化階段是執(zhí)行類構(gòu)造器<init>()方法的過(guò)程根盒。在以下四種情況下初始化過(guò)程會(huì)被觸發(fā)執(zhí)行:
      • 遇到new钳幅、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí)郑象,如果類沒(méi)有進(jìn)行過(guò)初始化贡这,則需先觸發(fā)其初始化。生成這4條指令的最常見(jiàn)的java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象厂榛、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾盖矫、已在編譯器把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用類的靜態(tài)方法的時(shí)候击奶。
      • 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候辈双。
      • 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化柜砾、則需要先出發(fā)其父類的初始化湃望。
      • jvm啟動(dòng)時(shí),用戶指定一個(gè)執(zhí)行的主類(包含main方法的那個(gè)類)痰驱,虛擬機(jī)會(huì)先初始化這個(gè)類证芭。
      • 類構(gòu)造器<init>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static塊)中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的担映,靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量废士,定義在它之后的變量,在前面的靜態(tài)語(yǔ)句快可以賦值蝇完,但是不能訪問(wèn)官硝。
      • 類構(gòu)造器<init>()方法與類的構(gòu)造函數(shù)(實(shí)例構(gòu)造函數(shù)<init>()方法)不同,它不需要顯式調(diào)用父類構(gòu)造短蜕,虛擬機(jī)會(huì)保證在子類<init>()方法執(zhí)行之前氢架,父類的<init>()方法已經(jīng)執(zhí)行完畢。因此在虛擬機(jī)中的第一個(gè)執(zhí)行的<init>()方法的類肯定是java.lang.Object朋魔。
      • 由于父類的<init>()方法先執(zhí)行岖研,也就意味著父類中定義的靜態(tài)語(yǔ)句快要優(yōu)先于子類的變量賦值操作。
      • <init>()方法對(duì)于類或接口來(lái)說(shuō)并不是必須的警检,如果一個(gè)類中沒(méi)有靜態(tài)語(yǔ)句缎玫,也沒(méi)有變量賦值的操作硬纤,那么編譯器可以不為這個(gè)類生成<init>()方法。
      • 接口中不能使用靜態(tài)語(yǔ)句塊赃磨,但接口與類不太能夠的是筝家,執(zhí)行接口的<init>()方法不需要先執(zhí)行父接口的<init>()方法。只有當(dāng)父接口中定義的變量被使用時(shí)邻辉,父接口才會(huì)被初始化溪王。另外,接口的實(shí)現(xiàn)類在初始化時(shí)也一樣不會(huì)執(zhí)行接口的<init>()方法值骇。
      • 虛擬機(jī)會(huì)保證一個(gè)類的<init>()方法在多線程環(huán)境中被正確加鎖和同步莹菱,如果多個(gè)線程同時(shí)去初始化一個(gè)類,那么只會(huì)有一個(gè)線程執(zhí)行這個(gè)類的<init>()方法吱瘩,其他線程都需要阻塞等待道伟,直到活動(dòng)線程執(zhí)行<init>()方法完畢。如果一個(gè)類的<init>()方法中有耗時(shí)很長(zhǎng)的操作使碾,那就可能造成多個(gè)進(jìn)程阻塞蜜徽。
    • 準(zhǔn)備階段和初始化階段看似有點(diǎn)牟盾,其實(shí)是不牟盾的票摇,
      如果類中有語(yǔ)句:private static int a = 10拘鞋,
      它的執(zhí)行過(guò)程是這樣的:首先字節(jié)碼文件被加載到內(nèi)存后,
      先進(jìn)行鏈接的驗(yàn)證這一步驟矢门,驗(yàn)證通過(guò)后準(zhǔn)備階段盆色,
      給a分配內(nèi)存,因?yàn)樽兞縜是static的祟剔,
      所以此時(shí)a等于int類型的默認(rèn)初始值0隔躲,
      即a=0,然后到解析,到初始化這一步驟時(shí)物延,
      才把a(bǔ)的真正的值10賦給a,此時(shí)a=10蹭越。
    • 加載(裝載)、驗(yàn)證教届、準(zhǔn)備、初始化和卸載這五個(gè)階段順序是固定的驾霜,類加載過(guò)程必須按照這種順序開(kāi)始案训,而解析階段不一定,它在某些情況下可以在初始化之后再開(kāi)始粪糙, 這是為了運(yùn)行時(shí)動(dòng)態(tài)綁定特性(JIT例如接口只在調(diào)用的時(shí)候才知道具體的實(shí)現(xiàn)的是哪個(gè)子類)强霎。值得注意的是:這些階段通常都是交叉的混合式進(jìn)行的,通常會(huì)在一個(gè)階段執(zhí)行的過(guò)程中調(diào)用或激活另一個(gè)階段蓉冈。
  • 類加載器
    JVM設(shè)計(jì)者把類加載階段中的通過(guò)類全名來(lái)獲取此類的二進(jìn)制字節(jié)流這個(gè)動(dòng)作放到j(luò)ava虛擬機(jī)外部去實(shí)現(xiàn)城舞,以便讓應(yīng)用程序決定如何獲取所需要的類轩触。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊成為“類加載器”。

  • 類與類加載器
    對(duì)于任何一個(gè)類家夺,都需要由加載它的類加載器和這個(gè)類來(lái)確定其在JVM中的唯一性脱柱。也就是說(shuō),兩個(gè)類來(lái)源于同一個(gè)Class文件拉馋,并且被同一個(gè)類加載器加載榨为,這兩個(gè)類才相等。

  • 雙親委派模型

    • 從虛擬機(jī)的角度來(lái)說(shuō)煌茴,有兩種不同的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader),該加載器使用C++語(yǔ)言實(shí)現(xiàn)随闺,屬于虛擬機(jī)自身的一部分。另一部分就是所有其它的類加載器蔓腐,這些類加載器是由Java語(yǔ)言實(shí)現(xiàn)矩乐,獨(dú)立于JVM外部,并且全部繼承抽象類java.lang.ClassLoader.
    • 從java開(kāi)發(fā)人員的角度看回论,大部分java程序會(huì)用到以下三種系統(tǒng)提供的類加載器:
      • 啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載JAVA_HOME\lib目錄中并且能被虛擬機(jī)識(shí)別的類庫(kù)加載到JVM內(nèi)存中散罕,如果名稱不符合的類庫(kù)即使在lib目錄中也不會(huì)被加載。該類加載器無(wú)法被java程序直接引用透葛。

      • 擴(kuò)展類加載器(Extension ClassLoader):該加載器主要負(fù)責(zé)加載JAVA_HOME\lib\ext目錄中的類庫(kù)笨使,開(kāi)發(fā)者可以使用擴(kuò)展加載器。

      • 應(yīng)用程序類加載器(Application ClassLoader):該列加載器也稱為系統(tǒng)加載器僚害,它負(fù)責(zé)加載用戶類路徑(Classpath)上所指定的類庫(kù)硫椰,開(kāi)發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器萨蚕,一般情況下這個(gè)就是程序中默認(rèn)的類加載器靶草。

      • 當(dāng)然除了以上三種類加載器,我們還能自己定義類加載器岳遥。這些類加載器之間的關(guān)系如下奕翔。

      • 類和類加載器
      • 上面的這種模型,就稱為類加載器的雙親委派模型浩蓉。該模型要求除了頂層的啟動(dòng)類加載器外派继,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。子類加載器不是以繼承的關(guān)系來(lái)實(shí)現(xiàn)捻艳,而是通過(guò)組合關(guān)系來(lái)復(fù)用父加載器的代碼驾窟。

      • 雙親委派模型的工作過(guò)程為:如果一個(gè)類加載器收到了類請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類认轨,而是把這個(gè)請(qǐng)求委派給父加載器去完成绅络,每一層都是如此,因此所有類加載的請(qǐng)求都會(huì)傳到啟動(dòng)類加載器,只有當(dāng)父加載器無(wú)法完成該請(qǐng)求時(shí)恩急,子加載器才去自己加載杉畜。
        雙親委派模型的好處就是java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如:Object,無(wú)論那個(gè)類加載器去加載該類衷恭,最終都是由啟動(dòng)類加載器進(jìn)行加載的此叠,因此Object類在程序的各種類加載環(huán)境中都是一個(gè)類。如果不用改模型匾荆,那么java.lang.Object類存放在classpath中拌蜘,那么系統(tǒng)中就會(huì)出現(xiàn)多個(gè)Object類,程序變得很混亂牙丽。

      • 一般來(lái)說(shuō)简卧,這四種類加載器會(huì)形成一種父子關(guān)系,高層為低層的父加載器烤芦。在類進(jìn)行加載時(shí)举娩,
        首先會(huì)自底向上挨個(gè)檢查是否已經(jīng)加載了指定類,如果已經(jīng)加載构罗,則直接返回該類的引用铜涉。
        如果到最高層也沒(méi)有找到加載過(guò)指定類,那么會(huì)自頂向下挨個(gè)嘗試加載遂唧,直到用戶自定義類加載器芙代,
        如果還不能成功,就會(huì)拋出異常盖彭。


        是否已經(jīng)加載和嘗試加載
      • 類加載java.lang.ClassLoader類中纹烹,有一個(gè)loadClass方法源碼如下:

       protected synchronized Class loadClass(String name, boolean resolve)
          throws ClassNotFoundException {
      // 首先檢查該name指定的class是否有被加載
      Class c = findLoadedClass(name);
      if (c == null) {
          try {
              if (parent != null) {
                  // 如果parent不為null,則調(diào)用parent的loadClass進(jìn)行加載
                  c = parent.loadClass(name, false);
              } else {
                  // parent為null召边,則調(diào)用BootstrapClassLoader進(jìn)行加載
                  c = findBootstrapClass0(name);
              }
          } catch (ClassNotFoundException e) {
              // 如果仍然無(wú)法加載成功铺呵,則調(diào)用自身的findClass進(jìn)行加載
              c = findClass(name);
          }
      }
      if (resolve) {
          resolveClass(c);
      }
        return c;
      }
      
      • 從源代碼中,我們不難看出隧熙,先檢查是否已經(jīng)加載片挂,如果沒(méi)有,就調(diào)用父加載器的loadClass()方法贞盯,如果父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器音念。如果父加載器加載失敗,拋出ClassNotFountException躏敢,然后再調(diào)用findClass()方法加載闷愤。
  • 自定義類加載器

    若要實(shí)現(xiàn)自定義類加載器,只需要繼承java.lang.ClassLoader類父丰,并且重寫findClass()方法即可。 java.lang.ClassLoader類的基本職責(zé)就是根據(jù)一個(gè)指定的類的名稱,找到或者生成對(duì)應(yīng)的字節(jié)碼蛾扇,然后從這些字節(jié)碼中定義出一個(gè)Java類攘烛,即java.lang.Class類的一個(gè)實(shí)例。除此之外镀首,ClassLoader還負(fù)責(zé)加載Java應(yīng)用所需的資源坟漱,如圖像文件和配置文件等,熱替換也是基于該類更哄,來(lái)繞過(guò)Java類的既定加載過(guò)程
    ClassLoader中與加載類相關(guān)的方法如下:

    方法                                說(shuō)明
    
    findLoadedClass():該方法會(huì)在對(duì)應(yīng)加載器的名字空間中尋找指定的類是否已存在芋齿,如果存在就返回給類的引用,
    否則就返回null成翩。每個(gè)類加載器都維護(hù)有自己的一份已加載類名字空間觅捆,其中不能出現(xiàn)兩個(gè)同名的類。
    凡是通過(guò)該類加載器加載的類麻敌,無(wú)論是直接的還是間接的栅炒,都保存在自己的名字空間中,這里的直接是指术羔,
    存在于該類加載器的加載路徑上并由該加載器完成加載赢赊,間接是指,由該類加載器把類的加載工作委托給其他類加載器完成類的實(shí)際加載级历。
    
    getSystemClassLoader():該方法返回系統(tǒng)使用的ClassLoader释移。可以在
    自定義的類加載器中通過(guò)該方法把一部分工作轉(zhuǎn)交給系統(tǒng)類加載器去處理寥殖。
    
    defineClass():該方法接收以字節(jié)數(shù)組表示的類字節(jié)碼玩讳,并把它轉(zhuǎn)換成Class實(shí)例。
    系統(tǒng)自帶的ClassLoader扛禽,默認(rèn)加載程序的是AppClassLoader锋边,
    ClassLoader加載一個(gè)class,最終調(diào)用的是defineClass(…)方法编曼,
    這時(shí)候就在想是否可以重復(fù)調(diào)用defineClass(…)方法加載同一個(gè)類(或者修改過(guò))豆巨,
    最后發(fā)現(xiàn)調(diào)用多次的話會(huì)有相關(guān)錯(cuò)誤:
    java.lang.LinkageError
    attempted duplicate class definition
    所以一個(gè)class被一個(gè)ClassLoader實(shí)例加載過(guò)的話,
    就不能再被這個(gè)ClassLoader實(shí)例再次加載(這里的加載指的是掐场,調(diào)用了defileClass(...)方法往扔,
    重新加載字節(jié)碼、解析熊户、驗(yàn)證)萍膛。而系統(tǒng)默認(rèn)的AppClassLoader加載器,
    他們內(nèi)部會(huì)緩存加載過(guò)的class嚷堡,重新加載的話蝗罗,就直接取緩存艇棕。
    所以對(duì)于熱加載的話,只能重新創(chuàng)建一個(gè)ClassLoader串塑,然后再去加載已經(jīng)被加載過(guò)的class文件沼琉。
    
    loadClass():加載類的入口方法,調(diào)用該方法完成類的顯式加載桩匪。通過(guò)對(duì)該方法的重寫打瘪,
    可以完全控制和管理類的加載過(guò)程。執(zhí)行l(wèi)oadClass方法傻昙,只是單純的把類加載到內(nèi)存闺骚,
    并不是對(duì)類的主動(dòng)使用,不會(huì)引起類的初始化妆档。
    該方法轉(zhuǎn)換一個(gè)類的同時(shí)僻爽,會(huì)先要求裝載該類的父類以及實(shí)現(xiàn)的接口類。是ClassLoader的入口點(diǎn)过吻。
    當(dāng)一個(gè)類沒(méi)有指明用什么加載器加載的時(shí)候进泼,JVM默認(rèn)采用AppClassLoader加載器
    加載沒(méi)有加載過(guò)的class,調(diào)用的方法的入口就是loadClass(…)纤虽。
    如果一個(gè)class被自定義的ClassLoader加載乳绕,
    那么JVM也會(huì)調(diào)用這個(gè)自定義的ClassLoader.loadClass(…)
    方法來(lái)加載class內(nèi)部引用的一些別的class文件。重載這個(gè)方法逼纸,
    能實(shí)現(xiàn)自定義加載class的方式洋措,會(huì)拋棄雙親委托機(jī)制,但是即使不采  用雙親委托機(jī)制杰刽,
    比如java.lang包中的相關(guān)類還是不能自定義一個(gè)同名的類來(lái)代替菠发,主要因?yàn)镴VM解析、
    驗(yàn)證class的時(shí)候贺嫂,會(huì)進(jìn)行相關(guān)判斷滓鸠。
    
    
    resolveClass():鏈接一個(gè)指定的類。這是一個(gè)在某些情況下確保類可用的必要方法第喳。
    
    • 總結(jié)
      • 要想實(shí)現(xiàn)同一個(gè)類的不同版本的共存糜俗,那么這些不同版本必須由不同的類加載器進(jìn)行加載,因此就不能把這些類的加載工作委托給系統(tǒng)加載器來(lái)完成曲饱,因?yàn)樗鼈冎挥幸环荨?/li>
      • 為了做到這一點(diǎn)悠抹,就不能采用系統(tǒng)默認(rèn)的類加載器委托規(guī)則,也就是說(shuō)我們定制的類加載器的父加載器必須設(shè)置為null扩淀。
  • 補(bǔ)充資料:關(guān)于類加載器的命名空間

    • 命名空間由加載器和所有的父加載器所加載的類構(gòu)成楔敌;
    • 在同一個(gè)命名空間中,不可能出現(xiàn)類名相同的兩個(gè)類驻谆;
    • 在不同的命名空間中卵凑,可能出現(xiàn)類名相同的兩個(gè)類(類名指類全稱)庆聘;
    • 由子加載器加載的類能看見(jiàn)父加載器加載的類,反之不可以勺卢;(比如java.lang.String類掏觉,我們自己寫的類肯定能看見(jiàn),但是父加載器肯定看不見(jiàn)我們自己定義的類)
    • 如果兩個(gè)加載器之間沒(méi)有直接或者間接的父子關(guān)系值漫,那么兩個(gè)加載器加載的類是相互不可見(jiàn)的;
  • 自定義ClassLoader

    • CustomClassLoader
      package com.coding.classloader;
      
      import org.apache.log4j.Logger;
      
      import java.io.*;
      import java.lang.reflect.Method;
      import java.net.URL;
      import java.util.HashSet;
      import java.util.Timer;
      import java.util.TimerTask;
      
      public class CustomClassLoader extends ClassLoader {
      
          private Logger logger = Logger.getLogger(CustomClassLoader.class);
          private String basedir; // 需要該類加載器直接加載的類文件的基目錄
          private HashSet classNames; // 需要由該類加載器直接加載的類名
      
          public CustomClassLoader(String basedir, String[] clazzNames) throws Exception {
              super(null); // 指定父類加載器為 null
              this.basedir = basedir;
      
              classNames = new HashSet();
              for (int i = 0; i < clazzNames.length; i++) {
                  String name = clazzNames[i];
      
      
                  StringBuffer sb = new StringBuffer(basedir);
                  String className = name.contains(".") ? name.replace('.', File.separatorChar)+ ".class" : name+ ".class";
                  sb.append(className);
      
      
                  logger.debug("Load Class: baseDir:" + basedir + ",ClassName:" + name + ",Class file path:" + sb.toString());
      
                  File classF = new File(sb.toString());
                  FileInputStream fin = new FileInputStream(classF);
                  byte[] raw = new byte[(int) classF.length()];
                  fin.read(raw);
                  fin.close();
      
                  defineClass(name, raw, 0, raw.length);
      
      
                  classNames.add(clazzNames[i]);
              }
          }
      
          @Override
          protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
              Class cls = null;
              cls = findLoadedClass(name);
              if (!this.classNames.contains(name) && cls == null)
                  cls = getSystemClassLoader().loadClass(name);
              if (cls == null)
                  throw new ClassNotFoundException(name);
              if (resolve)
                  resolveClass(cls);
              return cls;
          }
      
      
          public static void main(String[] args){
              new Timer().schedule(new TimerTask() {
      
                  @Override
                  public void run() {
                      try {
      
                          URL resource = CustomClassLoader.class.getResource("/");
                          String resourceFile = resource.getFile();
                          System.out.println(resourceFile);
                          // 每次都創(chuàng)建出一個(gè)新的類加載器
                          CustomClassLoader customClassLoader = new CustomClassLoader(
                                  resourceFile, new String[] { "com.coding.classloader.Foo" });
                          Class<?> cls = customClassLoader.loadClass("com.coding.classloader.Foo");
      
      
      //                    ClassLoader classLoader1 = cls.getClassLoader();//com.coding.classloader.CustomClassLoader
      //                    Class<?> cls2 = Class.forName("com.coding.classloader.Foo");
      //                    ClassLoader classLoader = cls2.getClassLoader();//sun.misc.Launcher$AppClassLoader
      //                    System.out.println(classLoader+":"+classLoader1);
      
                          Object foo = cls.newInstance();
      
                          Method m = foo.getClass().getMethod("sayHi", new Class[] {});
                          m.invoke(foo, new Object[] {});
                      } catch (Exception ex) {
                          ex.printStackTrace();
                      }
                  }
              }, 0, 1000L);
          }
      
      
      }
    
    
    • 嘗試熱加載的類
    package com.coding.classloader;
    
    public class Foo  {
          public void sayHi() {
              System.out.println("hi\t v1");
          }
    }
    
    • 測(cè)試
      • 運(yùn)行main方法,此時(shí)sayHi方法打印出hi v1
      • 修改sayHi方法织盼,使之打印出hi v2杨何,重新編譯,
      • 生成新class沥邻,覆蓋舊的class危虱,立馬生效,打印h1 v2
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唐全,一起剝皮案震驚了整個(gè)濱河市埃跷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌邮利,老刑警劉巖弥雹,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異延届,居然都是意外死亡剪勿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門方庭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)厕吉,“玉大人,你說(shuō)我怎么就攤上這事械念⊥分欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵龄减,是天一觀的道長(zhǎng)项钮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)欺殿,這世上最難降的妖魔是什么寄纵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮脖苏,結(jié)果婚禮上程拭,老公的妹妹穿的比我還像新娘。我一直安慰自己棍潘,他們只是感情好恃鞋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布崖媚。 她就那樣靜靜地躺著,像睡著了一般恤浪。 火紅的嫁衣襯著肌膚如雪畅哑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天水由,我揣著相機(jī)與錄音荠呐,去河邊找鬼。 笑死砂客,一個(gè)胖子當(dāng)著我的面吹牛泥张,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鞠值,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼媚创,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了彤恶?” 一聲冷哼從身側(cè)響起钞钙,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎声离,沒(méi)想到半個(gè)月后芒炼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡术徊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年焕议,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弧关。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盅安,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出世囊,到底是詐尸還是另有隱情别瞭,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布株憾,位于F島的核電站蝙寨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏嗤瞎。R本人自食惡果不足惜墙歪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贝奇。 院中可真熱鬧虹菲,春花似錦、人聲如沸掉瞳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至霎褐,卻和暖如春址愿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冻璃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工响谓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人省艳。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓歌粥,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親拍埠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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