程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java類加載器(ClassLoader)詳解

Java類加載器(ClassLoader)詳解

編輯:關於JAVA
 

本文主要講述Java ClassLoader的工作原理,這為後面將Android App代碼熱替換或者插件化升級做鋪墊

一、 類加載器

ClassLoader即常說的類加載器,其功能是用於從Class文件加載所需的類,主要場景用於熱部署、代碼熱替換等場景。

系統提供3種的類加載器:Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader

1.1 Bootstrap ClassLoader

啟動類加載器,一般由C++實現,是虛擬機的一部分。該類加載器主要職責是將JAVA_HOME路徑下的\lib目錄中能被虛擬機識別的類庫(比如rt.jar)加載到虛擬機內存中。Java程序無法直接引用該類加載器

1.2 Extension ClassLoader

擴展類加載器,由Java實現,獨立於虛擬機的外部。該類加載器主要職責將JAVA_HOME路徑下的\lib\ext目錄中的所有類庫,開發者可直接使用擴展類加載器。 該加載器是由sun.misc.Launcher$ExtClassLoader實現。

1.3 Application ClassLoader

應用程序類加載器,該加載器是由sun.misc.Launcher$AppClassLoader實現,該類加載器負責加載用戶類路徑上所指定的類庫。開發者可通過ClassLoader.getSystemClassLoader()方法直接獲取,故又稱為系統類加載器。當應用程序沒有自定義類加載器時,默認采用該類加載器。

ClassLoader.java

public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader(); //初始化系統類加載器 【見下文】
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        ClassLoader ccl = getCallerClassLoader();
        if (ccl != null && ccl != scl && !scl.isAncestor(ccl)) {
            sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
        }
    }
    return scl;
}

系統類加載器初始化:

private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
            Throwable oops = null;
            scl = l.getClassLoader();
            try {
                scl = AccessController.doPrivileged(
                    new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
                    oops = oops.getCause();
                }
            }
            if (oops != null) {
                if (oops instanceof Error) {
                    throw (Error) oops;
                } else {
                    throw new Error(oops);
                }
            }
        }
        sclSet = true;
    }
}

二、雙親委派模型

ClassLoader的雙親委派模型中,各個ClassLoader之間的關系是通過組合關系來復用父加載器。當一個ClassLoader收到來類加載的請求,首先把該請求委派該父類ClassLoader處理,當父類ClassLoader無法處理時,才由當前類ClassLoader來處理。對於每個ClassLoader這個方式,也就是父類的優先於子類處理類加載的請求,那麼也就是說任何一個請求第一次處理的便是最頂層的Bootstrap ClassLoader(啟動類加載器)。

類加載器的層級查找順序依次為:啟動類加載器,擴展類加載器,系統類加載器。系統類加載器是默認的應用程序類加載器。

classloader

這樣的好處是不同層次的類加載器具有不同優先級,比如所有Java對象的超級父類java.lang.Object,位於rt.jar,無論哪個類加載器加載該類,最終都是由啟動類加載器進行加載,保證安全。即使用戶自己編寫一個java.lang.Object類並放入程序中,雖能正常編譯,但不會被加載運行,保證不會出現混亂。那麼有人會繼續追問,如果自己再自定義一個類加載器來加載自己定義的java.lang.Object類呢? 這樣做也是不會成功的,虛擬機將會拋出一異常。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //檢查該類是否已經加載過
        Class c = findLoadedClass(name);
        if (c == null) {
            //如果該類沒有加載,則進入該分支
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //當父類的加載器不為空,則通過父類的loadClass來加載該類
                    c = parent.loadClass(name, false);
                } else {
                    //當父類的加載器為空,則調用啟動類加載器來加載該類
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //非空父類的類加載器無法找到相應的類,則拋出異常
            }

            if (c == null) {
                //當父類加載器無法加載時,則調用findClass方法來加載該類
                long t1 = System.nanoTime();
                c = findClass(name); //用戶可通過覆寫該方法,來自定義類加載器

                //用於統計類加載器相關的信息
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            //對類進行link操作
            resolveClass(c);
        }
        return c;
    }
}

當開發者需要自定義類加載器時,可通過覆寫loadClass()方法或者findClass()。

三、自定義類加載器

每一個ClassLoader都擁有自己獨立的類名稱空間,類是由ClassLoader將其加載到Java虛擬機中,故類是由加載它的ClassLoader和該類本身一起確定其在Java 運行時環境的唯一性。故只有同一個ClassLoader加載的同一個類,才能算是Java 運行時環境中的相同的兩個類。哪怕是來自同一個Class文件,即使被同一個虛擬機加載的兩個類,只要ClassLoader不同,那麼也屬於不同的類。對於equals()、isinstanceof()等方法來判斷對象的相等或所屬關系都是需要基於同一個ClassLoader。

自定義類加載器示例:

package com.yuanhh.classloader;

import java.io.IOException;
import java.io.InputStream;

public class ClassLoadDemo{

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

        ClassLoader clazzLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String clazzName = name.substring(name.lastIndexOf(".") + 1) + ".class";

                    InputStream is = getClass().getResourceAsStream(clazzName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };

        String currentClass = "com.yuanhh.classloader.ClassLoadDemo";
        Class<?> clazz = clazzLoader.loadClass(currentClass);
        Object obj = clazz.newInstance();

        System.out.println(obj.getClass());
        System.out.println(obj instanceof com.yuanhh.classloader.ClassLoadDemo);
    }
}

上面代碼的輸出結果:

class com.yuanhh.classloader.ClassLoadDemo
false

輸出結果的第一行,可以看出這個對象的確是com.yuanhh.classloader.ClassLoadDemo實例化的對象;但第二句是false,這是由於代碼中的obj是由用戶自定義的類加載器clazzLoader來加載的,可通過obj.getClass().getClassLoader()獲取該對象的類加載器為com.yuanhh.classloader.ClassLoadDemo$xxx,而虛擬機本身會由系統類加載器加載的類ClassLoadDemo,可通過ClassLoadDemo.class.getClassLoader()得其類加載器為sun.misc.Launcher$AppClassLoader@XXX。所以可得出結論:即使都是來自同一個Class文件,加載器不同,仍然是兩個不同的類,所以返回值是false。

通過Class.forName()方法加載的類,采用的是系統類加載器。

四、 經典應用場景

  • Tomcat,類加載器架構,自己定義了多個類加載器,
    • 保證了同一個服務器的兩個Web應用程序的Java類庫隔離;
    • 保證了同一個服務器的兩個Web應用程序的Java類庫又可以相互共享;比如多個Spring組織的應用程序不能共享,會造成資源浪費;
    • 保證了服務器盡可能保證自身的安全不受不受部署Web應用程序影響;
    • 支持JSP應用的服務器,大多需要支持熱替換(HotSwap)功能。
  • OSGi(Open Service GateWay Initiative),是基於Java語言的動態模塊化規范。已成為Java世界的“事實上”的模塊化標准,最為熟悉的案例的Eclipse IDE。
 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved