Java虛擬機--Tomcat類加載

Tomcat類加載

與很多服務(wù)器應(yīng)用一樣,Tomcat也實現(xiàn)了類加載器。

在Java虛擬機類加載的基礎(chǔ)上播瞳,Tomcat進行了稍許改動,以適應(yīng)自身業(yè)務(wù)的需求免糕。

當(dāng)Tomcat啟動后赢乓,它會創(chuàng)建一組類加載器忧侧,這些類加載器也會形成雙親委派模型中的父子關(guān)系,父類加載器在子類加載器之上:

   Bootstrap
      |
   System
      |
   Common
   /     \
Webapp1   Webapp2 ...

結(jié)合雙親委派模型來看牌芋,Tomcat的類加載結(jié)構(gòu)蚓炬,如圖所示:

image

Tomcat在原有雙親委派模型的基礎(chǔ)上,增加了Common類加載器躺屁、Catalina類加載器试吁、Shared類加載,以及WebApp類加載器楼咳。

如果將兩者結(jié)合起來看熄捍,那么Bootstrap對應(yīng)的是Bootstrap ClassLoader和Extension ClassLoader,System對應(yīng)的是Application ClassLoader母怜,Common對應(yīng)的是Common ClassLoader余耽、Catalina ClassLoader和Shared ClassLoader,WebApp1/WebApp2..對應(yīng)的是WebApp ClassLoader苹熏。

Bootstrap ClassLoader:C++編寫碟贾,無具體實現(xiàn)類,無法通過方法獲得轨域,負責(zé)加載<Java_Runtime_Home>/lib目錄下的文件袱耽。

Extension ClassLoader:Java程序編寫,sun.misc.Launcher.ExtClassLoader實現(xiàn)干发,負責(zé)加載<Java_Runtime_Home>/lib/ext目錄下的文件朱巨。

Application ClassLoader:Java語言編寫,sun.misc.Launcher.AppClassLoader實現(xiàn)枉长,負責(zé)加載Tomcat目錄下lib文件夾下的bootstrap.jar冀续、commons-daemon.jar、tomcat-juli.jar必峰。

Common ClassLoader:Java語言編寫洪唐,org.apache.catalina.loader.StandardClassLoader實現(xiàn)(在tomcat源碼中),負責(zé)加載Tomcat目錄conf文件夾下的catalina.properties文件中的common.loader屬性所指定的目錄吼蚁。

Catalina ClassLoader:Java語言編寫凭需,org.apache.catalina.loader.StandardClassLoader實現(xiàn)(在tomcat源碼中),負責(zé)加載Tomcat目錄conf文件夾下的catalina.properties文件中的server.loader屬性所指定的目錄肝匆。

Shared ClassLoader:Java語言編寫粒蜈,org.apache.catalina.loader.StandardClassLoader實現(xiàn)(在tomcat源碼中),負責(zé)加載Tomcat目錄conf文件夾下的catalina.properties文件中的shared.loader屬性所指定的目錄术唬。

WebApp ClassLoader:Java語言編寫薪伏,org.apache.catalina.loader.WebappClassLoader實現(xiàn)(在tomcat源碼中),負責(zé)加載Tomcat目錄webapps文件下中應(yīng)用里的/WEB-INF/lib和/WEB-INF/class.

Tomcat目錄結(jié)構(gòu)中粗仓,有三組目錄(/common/,/server/和shared/)可以存放公用Java類庫,以及第四組Web應(yīng)用程序目錄/WEB-INF/

放置在common目錄中:類庫可被Tomcat和所有的Web應(yīng)用程序共同使用嫁怀。

放置在server目錄中:類庫可被Tomcat使用设捐,但對所有的Web應(yīng)用程序都不可見。

放置在shared目錄中:類庫可被所有的Web應(yīng)用程序共同使用塘淑,但對Tomcat自己不可見萝招。

放置在/WebApp/WEB-INF目錄中:類庫僅可以被此Web應(yīng)用程序使用,對Tomcat和其他Web應(yīng)用程序都不可見存捺。
tomcat7.0目錄結(jié)構(gòu)

值得注意的是槐沼,在Tomcat的6.0版本之后,只有在tomcat/conf/catalina.properties文件中給server.loader屬性和share.loader屬性賦值后捌治,才會去建立Catalina ClassLoader和Shared ClassLoader兩個類加載器的實例岗钩,否則在用到這兩個類加載器的地方都會用Common ClassLoader的實例代替(org.apache.catalina.loader.StandardClassLoader),而/common肖油、/server和/shared目錄在Tomcat6.0之后就不存在了兼吓,取而代之的是/lib目錄。

默認類加載器配置

Tomcat類加載源碼

說完了森枪,Tomcat類加載的結(jié)構(gòu)视搏。本小節(jié),具體說下Tomcat的類加載機制县袱,想要說清楚浑娜,那必須還得從Tomcat源碼來入手。

找到org.apache.catalina.startup.Bootstrap類式散,發(fā)現(xiàn)該類中有main方法筋遭,沒錯這就是Tomcat的起點。

public static void main(String args[]) {
    if (daemon == null) {
        // 創(chuàng)建Bootstrap對象:
        Bootstrap bootstrap = new Bootstrap();
        try {
            // bootstrap初始化
            bootstrap.init();
        } catch (Throwable t) {
            handleThrowable(t);
            t.printStackTrace();
            return;
        }
        daemon = bootstrap;
    } else {
        Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }
    try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }
        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {

            // command為start杂数,代表著啟動:
            daemon.setAwait(true);
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stop")) {
            daemon.stopServer(args);
        } else if (command.equals("configtest")) {
            daemon.load(args);
            if (null==daemon.getServer()) {
                System.exit(1);
            }
            System.exit(0);
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist.");
        }
    } catch (Throwable t) {
        .....省略
    }
}

在main方法中宛畦,我們首先創(chuàng)建了一個Bootstrap對象,并調(diào)用其init()方法揍移。

在初始化結(jié)束后,判斷虛擬機傳入的指令反肋,通常為“start”那伐,開始加載、啟動流程石蔗,調(diào)用了daemon.load(args)罕邀、daemon.start()方法來實現(xiàn)。

public void init() throws Exception {
    setCatalinaHome();
    setCatalinaBase();
    // Tomcat類加載器初始化
    initClassLoaders();

    // 設(shè)置線程上下文類加載器:
    Thread.currentThread().setContextClassLoader(catalinaLoader);
    
    SecurityClassLoad.securityClassLoad(catalinaLoader);

    if (log.isDebugEnabled())
        log.debug("Loading startup class");

    // 創(chuàng)建Catalina對象:
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.newInstance();
    if (log.isDebugEnabled())
        log.debug("Setting startup class properties");
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method = startupInstance.getClass().getMethod(methodName, paramTypes);
    
    // 將Catalina對象中的 parentClassLoader屬性設(shè)置為sharedLoader類加載器
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;
}

在Bootstrap初始化方法中养距,有兩行值得注意诉探,(1)initClassLoaders:初始化Tomcat類加載器、(2)Thread.currentThread().setContextClassLoader(catalinaLoader):將catalinaLoader設(shè)置為線程上下文類加載器(打破了雙親委派模型)棍厌;

緊接著肾胯,又通過反射的方式竖席,創(chuàng)建了Catalina對象,并對Catalina對象中的parentClassLoader屬性重新賦值敬肚,賦值為sharedLoader類加載器毕荐,也就是common ClassLoader。注意艳馒,此處賦值的目的是為了后續(xù)創(chuàng)建WebApp ClassLoader使用憎亚。

private void initClassLoaders() {
    try {
        // Common類加載器  
        commonLoader = createClassLoader("common", null);
        if( commonLoader == null ) {
            commonLoader=this.getClass().getClassLoader();
        }
        // Catalina類加載器  
        catalinaLoader = createClassLoader("server", commonLoader);
        // Shared類加載器  
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

在初始化Tomcat類加載器方法中,我們總共創(chuàng)建了3大類加載器弄慰,分別為commonLoader第美、catalinaLoader、sharedLoader陆爽。默認情況下什往,在Tomcat6.0之后,commonLoader墓陈、catalinaLoader恶守、sharedLoader對象相同,并且均為org.apache.catalina.loader.StandardClassLoader實例贡必。

Tomcat類加載器初始化時兔港,很明確是指出catalina ClassLoader和shared ClassLoader類加載器的父類加載器為common ClassLoader類加載器,而common ClassLoader的父類加載器傳值為null仔拟,繼續(xù)調(diào)用createClassLoader()方法:

private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {
    // 從conf/catalina.properties中獲取具體的key:
    String value = CatalinaProperties.getProperty(name + ".loader");
    if ((value == null) || (value.equals("")))
        return parent;

    // 將common.loader中的值衫樊,替換成服務(wù)器上的絕對路徑,并以利花;相隔
    value = replace(value);

    List<Repository> repositories = new ArrayList<Repository>();

    // 遍歷value中的值:
    StringTokenizer tokenizer = new StringTokenizer(value, ",");
    while (tokenizer.hasMoreElements()) {
        String repository = tokenizer.nextToken().trim();
        if (repository.length() == 0) {
            continue;
        }
        ...省略

        // 將value中的值科侈,組裝成Repository對象,并存入到List集合中
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(
                    new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(
                    new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(
                    new Repository(repository, RepositoryType.DIR));
        }
    }
    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

在創(chuàng)建ClassLoader方法中炒事,我們解析catalina.properties文件中的common.loader屬性臀栈,默認值為${catalina.base}/lib,${catalina.base}/lib/.jar,${catalina.home}/lib,${catalina.home}/lib/.jar**,將默認值中的${}進行替換挠乳,最終得到了服務(wù)器上的絕對路徑权薯,例如:(catalina.base,catalina.home是環(huán)境變量中的值,可通過System.getProperty("xxx")獲得)

F:\framework_code\apache-tomcat-7.0.88-src/lib,
F:\framework_code\apache-tomcat-7.0.88-src/lib/*.jar,
F:\framework_code\apache-tomcat-7.0.88-src/lib,
F:\framework_code\apache-tomcat-7.0.88-src/lib/*.jar

在得到這些絕對路徑后睡扬,進行Repository對象組裝盟蚣,并添加到ArrayList集合中來。最后卖怜,調(diào)用ClassLoaderFactory.createClassLoader(repositories, parent)生成具體的ClassLoader實現(xiàn)類屎开。

在創(chuàng)建common ClassLoader中:commonLoader = createClassLoader("common", null),傳遞父類加載器為parent = null马靠,當(dāng)實際創(chuàng)建類加載器對象時奄抽,會以系統(tǒng)類加載器(App ClassLoader)作為common ClassLoader的父類加載器蔼两。

說完了Common ClassLoader、Catalina ClassLoader如孝、Shared ClassLoader的實例化宪哩,我們接下來在來看看WebApp ClassLoader在Tomcat中是如何初始化的!5谖锁孟!

之前,在創(chuàng)建Catalina對象時候茁瘦,我們說到了對Catalina對象中的parentClassLoader屬性重新賦值品抽,是為了創(chuàng)建WebApp ClassLoader時使用。具體如何甜熔,我們來看下源碼T残簟!

由于Tomcat代碼太多腔稀,本次不展示代碼的流轉(zhuǎn)過程盆昙,直接來看源碼!

org.apache.catalina.loader.WebappClassLoader創(chuàng)建源碼焊虏,存在于org.apache.catalina.loader.WebappLoader類的createClassLoader()方法中:

private WebappClassLoaderBase createClassLoader() throws Exception {

    Class<?> clazz = Class.forName(loaderClass);
    WebappClassLoaderBase classLoader = null;

    if (parentClassLoader == null) {
        parentClassLoader = container.getParentClassLoader();
    }
    Class<?>[] argTypes = { ClassLoader.class };
    Object[] args = { parentClassLoader };
    Constructor<?> constr = clazz.getConstructor(argTypes);
    classLoader = (WebappClassLoaderBase) constr.newInstance(args);

    return classLoader;
}

loaderClass是WebappLoader的實例變量淡喜,其值為org.apache.catalina.loader.WebappClassLoader,通過反射調(diào)用了WebappClassLoader的構(gòu)造函數(shù)诵闭,然后傳遞了sharedLoader(也就是common ClassLoader)作為其父親加載器炼团。

來看下org.apache.catalina.loader.WebappClassLoader的構(gòu)造器:

public WebappClassLoader(ClassLoader parent) {
    super(parent);
}

由于WebappClassLoader繼承org.apache.catalina.loader.WebappClassLoaderBase類,所以來看看WebappClassLoaderBase類的構(gòu)造:

public WebappClassLoaderBase(ClassLoader parent) {

    super(new URL[0], parent);

    ClassLoader p = getParent();
    if (p == null) {
        p = getSystemClassLoader();
    }
    this.parent = p;

    ClassLoader j = String.class.getClassLoader();
    if (j == null) {
        j = getSystemClassLoader();
        while (j.getParent() != null) {
            j = j.getParent();
        }
    }
    this.j2seClassLoader = j;

    securityManager = System.getSecurityManager();
    if (securityManager != null) {
        refreshPolicy();
    }
}

將父類加載傳入疏尿,賦值給parent屬性切端,為sharedLoader(也就是common ClassLoader)吩抓。此外捆姜,繼續(xù)對j2seClassLoader屬性賦值炭臭,通過遍歷獲取到了sun.misc.Launcher.ExtClassLoader類加載。

至此敌呈,整個Tomcat類加載器初始化流程結(jié)束嚼鹉。

工作中遇到的類加載問題

上面我們說了Tomcat的類加載機制。

其中驱富,關(guān)于Tomcat的類加載器加載路徑,筆者提到了跟/conf/catalina.properties文件有關(guān)匹舞。

在該文件中褐鸥,可以自定義common.loader屬性,修改此屬性后赐稽,其他目錄下的.jar包叫榕、.class文件可以通過Tomcat的common ClassLoader加載進來浑侥。

在筆者工作的環(huán)境中,Tomcat的此屬性就被進行了修改晰绎,將應(yīng)用中maven所有依賴的jar包都放到了common.loader所配置的目錄下寓落,這樣做的結(jié)果就是:common ClassLoader將加載應(yīng)用中依賴的jar包,而應(yīng)用本身的WebappClassLoader失去了原本存在的意義荞下。不過伶选,這么做倒沒什么關(guān)系,只是在代碼開發(fā)時尖昏,會出現(xiàn)莫名的異常仰税。

image

截圖中,就是筆者所在公司的項目結(jié)構(gòu)抽诉,使用了maven的模塊化功能陨簇。

將hessian打成war包,進行Tomcat部署迹淌,hessian中如果沒有servlet的話河绽,基本上沒有業(yè)務(wù)代碼。

facade是對外的接口層唉窃,core是底層實現(xiàn)耙饰。

hessian依賴facade、core模塊句携。

編寫測試代碼:

在hessian模塊下創(chuàng)建servlet類榔幸,并調(diào)用core模塊中的任意一個類。

public class TestClassLoaderServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
        super.service(req, resp);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
        super.doGet(req, resp);
        TestUtil.test();
    }
}

在core模塊中矮嫉,類加載hessian模塊下的創(chuàng)建servlet類削咆,使用Class.forName("xxxx")即可。

public static void test(){
    try{
        Class clazz =  Class.forName("com.jiaboyan.hessian.TestClassLoaderServlet");
        logger.info("要加載的類為:"蠢笋,clazz);
    }catch (Throwable th){
        logger.error("異常",th);
    }
}

在本地對hessian進行打包拨齐,結(jié)果如下:

image

項目中的依賴,以及core模塊昨寞、facade模塊都會被打到hessian.war/WEB-INF/lib下瞻惋。

本地tomcat啟動,可以在idea中啟動援岩,也可以放到Tomcat目錄中去啟動也行歼狼。

輸出結(jié)果如下:

要加載的類為:class com.jiaboyan.hessian.TestClassLoaderServlet

一切都是如此的正常。

此時享怀,你的新功能開發(fā)完了羽峰,自測完了,你需要部署到測試服務(wù)器中。利用jenkins進行部署梅屉,公司的jenkins進行了修改值纱,在部署構(gòu)建過程中,會將war包中/WEB-INF/lib下的所有jar包坯汤,拷到Tomcat/commonlib目錄下虐唠,并刪除/WEB-INF/lib下的所有jar包。

而我們公司的Tomcat又對/conf/catalina.properties中的common.loader進行了修改:

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,${catalina.home}/commonlib/*.jar,${catalina.home}/commoncfg

通過common.loader的配置可知惰聂,common ClassLoader會加載commonlib目錄下的.jar包疆偿。

如果此時你發(fā)現(xiàn)了問題,那么說明你已經(jīng)理解了Tomcat的類加載機制庶近。

由此可見翁脆,對于core模塊、facade模塊下的代碼鼻种,均有common ClassLoader進行加載反番,而hessian模塊中的代碼是由WebApp ClassLoader進行加載。并且叉钥,common ClassLoader是WebApp ClassLoader的父類加載器罢缸,父類加載無法加載子類加載器的資源,當(dāng)測試服務(wù)器中代碼執(zhí)行時候投队,common ClassLoader無法加載WebApp ClassLoader所擁有的類枫疆,結(jié)果就是類加載異常。

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敷鸦,一起剝皮案震驚了整個濱河市息楔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扒披,老刑警劉巖值依,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碟案,居然都是意外死亡愿险,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門价说,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辆亏,“玉大人,你說我怎么就攤上這事鳖目“邕叮” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵领迈,是天一觀的道長甫匹。 經(jīng)常有香客問我甸鸟,道長,這世上最難降的妖魔是什么兵迅? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮薪贫,結(jié)果婚禮上恍箭,老公的妹妹穿的比我還像新娘。我一直安慰自己瞧省,他們只是感情好扯夭,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鞍匾,像睡著了一般交洗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上橡淑,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天构拳,我揣著相機與錄音,去河邊找鬼梁棠。 笑死置森,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的符糊。 我是一名探鬼主播凫海,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼男娄!你這毒婦竟也來了行贪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤模闲,失蹤者是張志新(化名)和其女友劉穎建瘫,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體围橡,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡暖混,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了翁授。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拣播。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖收擦,靈堂內(nèi)的尸體忽然破棺而出贮配,到底是詐尸還是另有隱情,我是刑警寧澤塞赂,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布泪勒,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏圆存。R本人自食惡果不足惜叼旋,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沦辙。 院中可真熱鬧夫植,春花似錦、人聲如沸油讯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陌兑。三九已至沈跨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間兔综,已是汗流浹背饿凛。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留邻奠,地道東北人笤喳。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像碌宴,于是被迫代替她去往敵國和親杀狡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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

  • 可悲的是贰镣,子欲養(yǎng)而親不待呜象。然而更可悲的是,父母還在碑隆,你卻不想養(yǎng)
    隨編簡閱讀 120評論 0 0