jvm類加載機(jī)制

當(dāng)程序使用某個(gè)類時(shí)兽埃,如果該類還沒被初始化板丽,加載到內(nèi)存中,則系統(tǒng)會(huì)通過加載刹勃、連接堪侯、初始化三個(gè)過程來對(duì)該類進(jìn)行初始化。該過程就被稱為類的初始化

類加載

指將類的class文件讀入內(nèi)存荔仁,并為之創(chuàng)建一個(gè)java.lang.Class的對(duì)象

類文件來源
  • 從本地文件系統(tǒng)加載的class文件
  • 從JAR包加載class文件
  • 從網(wǎng)絡(luò)加載class文件
  • 把一個(gè)Java源文件動(dòng)態(tài)編譯伍宦,并執(zhí)行加載

類加載器通常無須等到“首次使用”該類時(shí)才加載該類,JVM允許系統(tǒng)預(yù)先加載某些類

類加載器

類加載器就是負(fù)責(zé)加載所有的類咕晋,將其載入內(nèi)存中,生成一個(gè)java.lang.Class實(shí)例收奔。一旦一個(gè)類被加載到JVM中之后掌呜,就不會(huì)再次載入了。

img
img
  • 根類加載器(Bootstrap ClassLoader):其負(fù)責(zé)加載Java的核心類坪哄,比如String质蕉、System這些類
  • 拓展類加載器(Extension ClassLoader):其負(fù)責(zé)加載JRE的拓展類庫(kù)
  • 系統(tǒng)類加載器(System ClassLoader):其負(fù)責(zé)加載CLASSPATH環(huán)境變量所指定的JAR包和類路徑
  • 用戶類加載器:用戶自定義的加載器势篡,以類加載器為父類

類加載器之間的父子關(guān)系并不是繼承關(guān)系,是類加載器實(shí)例之間的關(guān)系

    public static void main(String[] args) throws IOException {
        ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系統(tǒng)類加載");
        Enumeration<URL> em1 = systemLoader.getResources("");
        while (em1.hasMoreElements()) {
            System.out.println(em1.nextElement());
        }
        ClassLoader extensionLader = systemLoader.getParent();
        System.out.println("拓展類加載器" + extensionLader);
        System.out.println("拓展類加載器的父" + extensionLader.getParent());
    }

結(jié)果

系統(tǒng)類加載
file:/E:/gaode/em/bin/
拓展類加載器sun.misc.Launcher$ExtClassLoader@6d06d69c
拓展類加載器的父null

為什么根類加載器為NULL?

根類加載器并不是Java實(shí)現(xiàn)的模暗,而且由于程序通常須訪問根加載器禁悠,因此訪問擴(kuò)展類加載器的父類加載器時(shí)返回NULL

JVM類加載機(jī)制

  • 全盤負(fù)責(zé),當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí)兑宇,該Class所依賴的和引用的其他Class也將由該類加載器負(fù)責(zé)載入碍侦,除非顯示使用另外一個(gè)類加載器來載入
  • 父類委托,先讓父類加載器試圖加載該類隶糕,只有在父類加載器無法加載該類時(shí)才嘗試從自己的類路徑中加載該類
  • 緩存機(jī)制瓷产,緩存機(jī)制將會(huì)保證所有加載過的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí)枚驻,類加載器先從緩存區(qū)尋找該Class濒旦,只有緩存區(qū)不存在,系統(tǒng)才會(huì)讀取該類對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)再登,并將其轉(zhuǎn)換成Class對(duì)象尔邓,存入緩存區(qū)。這就是為什么修改了Class后锉矢,必須重啟JVM梯嗽,程序的修改才會(huì)生效

URLClassLoader類

URLClassLoader為ClassLoader的一個(gè)實(shí)現(xiàn)類,該類也是系統(tǒng)類加載器和拓展類加載器的父類(繼承關(guān)系)沈撞。它既可以從本地文件系統(tǒng)獲取二進(jìn)制文件來加載類慷荔,也可以遠(yuǎn)程主機(jī)獲取二進(jìn)制文件來加載類。

兩個(gè)構(gòu)造器

URLClassLoader(URL[] urls):使用默認(rèn)的父類加載器創(chuàng)建一個(gè)ClassLoader對(duì)象缠俺,該對(duì)象將從urls所指定的路徑來查詢并加載類

URLClassLoader(URL[] urls,ClassLoader parent):使用指定的父類加載器創(chuàng)建一個(gè)ClassLoader對(duì)象显晶,其他功能與前一個(gè)構(gòu)造器相同

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import com.mysql.jdbc.Driver;

public class GetMysql {
    private static Connection conn;
    public static Connection getConn(String url,String user,String pass) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{
        if(conn==null){
            URL[]urls={new URL("file:mysql-connector-java-5.1.18.jar")};
            URLClassLoader myClassLoader=new URLClassLoader(urls);
            Driver driver=(Driver) myClassLoader.loadClass("com.mysql.jdbc.Driver").newInstance();
            Properties pros=new Properties();
            pros.setProperty("user", user);
            pros.setProperty("password", pass);
            conn=driver.connect(url, pros);
        }
        return conn;
    }
    public static method1 getConn() throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException{
        
            URL[]urls={new URL("file:com.em")};
            URLClassLoader myClassLoader=new URLClassLoader(urls);
            method1 driver=(method1) myClassLoader.loadClass("com.em.method1").newInstance();
        
        return driver;
    }
    
    public static void main(String[] args) throws MalformedURLException, InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException {
        System.out.println(getConn("jdbc:mysql://10.10.16.11:3306/auto?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true", "jiji", "jiji"));
        System.out.println(getConn());
    }
}

獲得URLClassLoader對(duì)象后,調(diào)用loanClass()方法來加載指定的類

自定義類加載器

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;

public class CompileClassLoader extends ClassLoader

{

    // 讀取一個(gè)文件的內(nèi)容

    @SuppressWarnings("resource")
    private byte[] getBytes(String filename) throws IOException

    {

        File file = new File(filename);

        long len = file.length();

        byte[] raw = new byte[(int) len];

        FileInputStream fin = new FileInputStream(file);

        // 一次讀取class文件的全部二進(jìn)制數(shù)據(jù)

        int r = fin.read(raw);

        if (r != len)

            throw new IOException("無法讀取全部文件" + r + "!=" + len);

        fin.close();
        return raw;

    }

    // 定義編譯指定java文件的方法

    private boolean compile(String javaFile) throws IOException

    {

        System.out.println("CompileClassLoader:正在編譯" + javaFile + "……..");

        // 調(diào)用系統(tǒng)的javac命令

        Process p = Runtime.getRuntime().exec("javac" + javaFile);

        try {

            // 其它線程都等待這個(gè)線程完成

            p.waitFor();

        } catch (InterruptedException ie)

        {

            System.out.println(ie);

        }

        // 獲取javac 的線程的退出值

        int ret = p.exitValue();

        // 返回編譯是否成功

        return ret == 0;

    }

    // 重寫Classloader的findCLass方法

    protected Class<?> findClass(String name) throws ClassNotFoundException

    {

        Class clazz = null;

        // 將包路徑中的.替換成斜線/

        String fileStub = name.replace(".", "/");

        String javaFilename = fileStub + ".java";

        String classFilename = fileStub + ".class";

        File javaFile = new File(javaFilename);

        File classFile = new File(classFilename);

        // 當(dāng)指定Java源文件存在壹士,且class文件不存在磷雇,或者Java源文件的修改時(shí)間比class文件//修改時(shí)間晚時(shí),重新編譯

        if (javaFile.exists() && (!classFile.exists())
                || javaFile.lastModified() > classFile.lastModified())

        {

            try {

                // 如果編譯失敗躏救,或該Class文件不存在

                if (!compile(javaFilename) || !classFile.exists())

                {

                    throw new ClassNotFoundException("ClassNotFoundException:"
                            + javaFilename);

                }

            } catch (IOException ex)

            {

                ex.printStackTrace();

            }

        }

        // 如果class文件存在唯笙,系統(tǒng)負(fù)責(zé)將該文件轉(zhuǎn)化成class對(duì)象

        if (classFile.exists())

        {

            try {

                // 將class文件的二進(jìn)制數(shù)據(jù)讀入數(shù)組

                byte[] raw = getBytes(classFilename);

                // 調(diào)用Classloader的defineClass方法將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成class對(duì)象

                clazz = defineClass(name, raw, 0, raw.length);

            } catch (IOException ie)

            {

                ie.printStackTrace();

            }

        }

        // 如果claszz為null,表明加載失敗,則拋出異常

        if (clazz == null) {

            throw new ClassNotFoundException(name);

        }

        return clazz;

    }

    // 定義一個(gè)主方法

    public static void main(String[] args) throws Exception

    {

        // 如果運(yùn)行該程序時(shí)沒有參數(shù)盒使,即沒有目標(biāo)類

        if (args.length < 1) {

            System.out.println("缺少運(yùn)行的目標(biāo)類崩掘,請(qǐng)按如下格式運(yùn)行java源文件:");

            System.out.println("java CompileClassLoader ClassName");

        }

        // 第一個(gè)參數(shù)是需要運(yùn)行的類

        String progClass = args[0];

        // 剩下的參數(shù)將作為運(yùn)行目標(biāo)類時(shí)的參數(shù),所以將這些參數(shù)復(fù)制到一個(gè)新數(shù)組中

        String progargs[] = new String[args.length - 1];

        System.arraycopy(args, 1, progargs, 0, progargs.length);

        CompileClassLoader cl = new CompileClassLoader();

        // 加載需要運(yùn)行的類

        Class<?> clazz = cl.loadClass(progClass);

        // 獲取需要運(yùn)行的類的主方法

        Method main = clazz.getMethod("main", (new String[0]).getClass());

        Object argsArray[] = { progargs };

        main.invoke(null, argsArray);

    }

}

JVM中除了根類加載器之外的所有類的加載器都是ClassLoader子類的實(shí)例少办,通過重寫ClassLoader中的方法苞慢,實(shí)現(xiàn)自定義的類加載器

loadClass(String name,boolean resolve):為ClassLoader的入口點(diǎn),根據(jù)指定名稱來加載類英妓,系統(tǒng)就是調(diào)用ClassLoader的該方法來獲取制定累對(duì)應(yīng)的Class對(duì)象

findClass(String name):根據(jù)指定名稱來查找類

推薦使用findClass方法

類的鏈接

當(dāng)類被加載后挽放,系統(tǒng)會(huì)為之生成一個(gè)Class對(duì)象绍赛,接著將會(huì)進(jìn)入連接階段,鏈接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到JRE中

三個(gè)階段

驗(yàn)證:檢驗(yàn)被加載的類是否有正確的內(nèi)部結(jié)構(gòu)辑畦,并和其他類協(xié)調(diào)一致

準(zhǔn)備:負(fù)責(zé)為類的類變量分配內(nèi)存吗蚌。并設(shè)置默認(rèn)初始值

解析:將類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換成直接引用

類的初始化

JVM負(fù)責(zé)對(duì)類進(jìn)行初始化,主要對(duì)類變量進(jìn)行初始化

在Java中對(duì)類變量進(jìn)行初始值設(shè)定有兩種方式:①聲明類變量是指定初始值②使用靜態(tài)代碼塊為類變量指定初始值

JVM初始化步驟

  1. 假如這個(gè)類還沒有被加載和連接纯出,則程序先加載并連接該類
  2. 假如該類的直接父類還沒有被初始化蚯妇,則先初始化其直接父類
  3. 假如類中有初始化語句,則系統(tǒng)依次執(zhí)行這些初始化語句

類初始化時(shí)機(jī)

  1. 創(chuàng)建類實(shí)例潦刃。也就是new的方式
  2. 調(diào)用某個(gè)類的類方法
  3. 訪問某個(gè)類或接口的類變量侮措,或?yàn)樵擃愖兞抠x值
  4. 使用反射方式強(qiáng)制創(chuàng)建某個(gè)類或接口對(duì)應(yīng)的java.lang.Class對(duì)象
  5. 初始化某個(gè)類的子類,則其父類也會(huì)被初始化
  6. 直接使用java.exe命令來運(yùn)行某個(gè)主類
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乖杠,一起剝皮案震驚了整個(gè)濱河市分扎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胧洒,老刑警劉巖畏吓,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異卫漫,居然都是意外死亡菲饼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門列赎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宏悦,“玉大人,你說我怎么就攤上這事包吝”罚” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵诗越,是天一觀的道長(zhǎng)砖瞧。 經(jīng)常有香客問我,道長(zhǎng)嚷狞,這世上最難降的妖魔是什么块促? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮床未,結(jié)果婚禮上竭翠,老公的妹妹穿的比我還像新娘。我一直安慰自己薇搁,他們只是感情好斋扰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般褥实。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上裂允,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天损离,我揣著相機(jī)與錄音,去河邊找鬼绝编。 笑死僻澎,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的十饥。 我是一名探鬼主播窟勃,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼逗堵!你這毒婦竟也來了秉氧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蜒秤,失蹤者是張志新(化名)和其女友劉穎汁咏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體作媚,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡攘滩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纸泡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漂问。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖女揭,靈堂內(nèi)的尸體忽然破棺而出蚤假,到底是詐尸還是另有隱情,我是刑警寧澤田绑,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布勤哗,位于F島的核電站,受9級(jí)特大地震影響掩驱,放射性物質(zhì)發(fā)生泄漏芒划。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一欧穴、第九天 我趴在偏房一處隱蔽的房頂上張望民逼。 院中可真熱鬧,春花似錦涮帘、人聲如沸拼苍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疮鲫。三九已至吆你,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俊犯,已是汗流浹背妇多。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留燕侠,地道東北人者祖。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绢彤,于是被迫代替她去往敵國(guó)和親七问。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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