java安全沙箱(一)之ClassLoader雙親委派機制

 java是一種類型安全的語言,它有四類稱爲安全沙箱機制的安全機制來保證語言的安全性,這四類安全沙箱分別是:java

本篇博客主要介紹「類加載體系」的基本原理;如需瞭解其它幾類安全機制能夠經過上面的博客連接進入查看。app

簡介

「類加載體系」及ClassLoader雙親委派機制。java程序中的 .java文件編譯完會生成 .class文件,而 .class文件就是經過被稱爲類加載器的ClassLoader加載的,而ClassLoder在加載過程當中會使用「雙親委派機制」來加載 .class文件,先上圖:jvm

看着圖從上往下介紹:函數

  1. BootStrapClassLoader:啓動類加載器,該ClassLoader是jvm在啓動時建立的,用於加載 $JAVA_HOME/jre/lib下面的類庫(或者經過參數-Xbootclasspath指定)。因爲引導類加載器涉及到虛擬機本地實現細節,開發者沒法直接獲取到啓動類加載器的引用,因此不能直接經過引用進行操做。測試

  2. ExtClassLoader:擴展類加載器,該ClassLoader是在sun.misc.Launcher裏做爲一個內部類ExtClassLoader定義的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader會加載 $JAVA_HOME/jre/lib/ext下的類庫(或者經過參數-Djava.ext.dirs指定)。ui

  3. AppClassLoader:應用程序類加載器,該ClassLoader一樣是在sun.misc.Launcher裏做爲一個內部類AppClassLoader定義的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader會加載java環境變量CLASSPATH所指定的路徑下的類庫,而CLASSPATH所指定的路徑能夠經過System.getProperty("java.class.path")獲取;固然,該變量也能夠覆蓋,可使用參數-cp,例如:java -cp 路徑 (能夠指定要執行的class目錄)。

  4. CustomClassLoader:自定義類加載器,該ClassLoader是指咱們自定義的ClassLoader,好比tomcat的StandardClassLoader屬於這一類;固然,大部分狀況下使用AppClassLoader就足夠了。

ClassLoader初始化源碼

下面貼下jdk關於類加載的源碼,上述四種類加載器中CustomClassLoader是用戶自定義的,BootStrapClassLoader是jvm建立的,就不展現了;這裏展現下AppClassLoader和ExtClassLoader的啓動過程,前面介紹過,AppClassLoader和ExtClassLoader都是在sun.misc.Launcher裏定義的,而個人sun.misc.Launcher沒有源碼,你們將就看看反編譯的代碼吧。若是想看sun.*包下的類源碼,你們能夠下載openjdk來查看。

public Launcher(){
        ExtClassLoader extclassloader;
        try{
            extclassloader = ExtClassLoader.getExtClassLoader();
        }
        catch(IOException ioexception) {
            throw new InternalError("Could not create extension class loader");
        }
        try{
            loader = AppClassLoader.getAppClassLoader(extclassloader);
        }
        catch(IOException ioexception1){
            throw new InternalError("Could not create application class loader");
        }
        Thread.currentThread().setContextClassLoader(loader);
        String s = System.getProperty("java.security.manager");
        if(s != null){
            SecurityManager securitymanager = null;
            if("".equals(s) || "default".equals(s))
                securitymanager = new SecurityManager();
            else
                try{
                    securitymanager = (SecurityManager)loader.loadClass(s).newInstance();
                }
                catch(IllegalAccessException illegalaccessexception) { }
                catch(InstantiationException instantiationexception) { }
                catch(ClassNotFoundException classnotfoundexception) { }
                catch(ClassCastException classcastexception) { }
            if(securitymanager != null)
                System.setSecurityManager(securitymanager);
            else
                throw new InternalError((new StringBuilder()).append("Could not create SecurityManager: ").append(s).toString());
        }
    }

能夠看到在Launcher構造函數的執行過程以下:

  1. 經過ExtClassLoader.getExtClassLoader()建立了ExtClassLoader;

  2. 經過AppClassLoader.getAppClassLoader(ExtClassLoader)建立了AppClassLoader,並將ExtClassLoader設爲AppClassLoader的parent ClassLoader;

  3. 經過Thread.currentThread().setContextClassLoader(loader)把AppClassLoader設爲線程的上下文 ClassLoader;

  4. 根據jvm參數-Djava.security.manager建立安全管理器(安全管理器的相關內容會在後續博客安全管理器及Java API中介紹),此時jvm會設置系統屬性"java.security.manager"爲空字符串""。

再貼下ExtClassLoader源碼:

public static ExtClassLoader getExtClassLoader()
            throws IOException{
            File afile[] = getExtDirs();
            return (ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction(afile) {
                public Object run() throws IOException{
                    int i = dirs.length;
                    for(int j = 0; j < i; j++)
                        MetaIndex.registerDirectory(dirs[j]);
                    return new ExtClassLoader(dirs);
                }

                final File val$dirs[];

                {
                    dirs = afile;
                    super();
                }
            });
            PrivilegedActionException privilegedactionexception;
            privilegedactionexception;
            throw (IOException)privilegedactionexception.getException();
        }
        
private static File[] getExtDirs()
        {
            String s = System.getProperty("java.ext.dirs");
            File afile[];
            if(s != null)
            {
                StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
                int i = stringtokenizer.countTokens();
                afile = new File[i];
                for(int j = 0; j < i; j++)
                    afile[j] = new File(stringtokenizer.nextToken());

            } else
            {
                afile = new File[0];
            }
            return afile;
        }

 

反編譯的源碼,你們將就看下;這裏你們關注下getExtDirs()這個方法,它會獲取屬性"java.ext.dirs"所對應的值,而後經過系統分隔符分割,而後加載分割後的字符串對應的目錄做爲ClassLoader的類加載庫。

下面看看AppClassLoader源碼:

public static ClassLoader getAppClassLoader(ClassLoader classloader) throws IOException{
            String s = System.getProperty("java.class.path");
            File afile[] = s != null ? Launcher.getClassPath(s) : new File[0];
            return (AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(s, afile, classloader) {
                public Object run() {
                    URL aurl[] = s != null ? Launcher.pathToURLs(path) : new URL[0];
                    return new AppClassLoader(aurl, extcl);
                }

                final String val$s;
                final File val$path[];
                final ClassLoader val$extcl;

                {
                    s = s1;
                    path = afile;
                    extcl = classloader;
                    super();
                }
            });
        }

 

 

首先獲取"java.class.path"對應的屬性,並轉換爲URL[]並設置爲ClassLoader的類加載庫,注意這裏的方法入參classloader就是ExtClassLoader,在創AppClassLoader會傳入ExtClassLoader做爲parent ClassLoader。

上面就是ClassLoader的啓動和初始化過程,後面會把loader做爲應用程序的默認ClassLoader使用,看下面的測試用例:

package com.jvm.study.classload;

public class Test {
    public static void main(String... args) {
        ClassLoader loader = Test.class.getClassLoader();
        System.err.println("loader:"+loader);
        while (loader != null) {
            loader = loader.getParent();
            System.err.println("in while:"+loader);
        }
    }
}

能夠看到ClassLoader的層次結構,輸出結果爲:

 

ClassLoader雙親委派機制源碼

前面談到了ClassLoader的幾類加載器,而ClassLoader使用雙親委派機制來加載class文件的。

ClassLoader的雙親委派機制是這樣的(這裏先忽略掉自定義類加載器CustomClassLoader):

  1. 當AppClassLoader加載一個class時,它首先不會本身去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。

  2. 當ExtClassLoader加載一個class時,它首先也不會本身去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。

  3. 若是BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用ExtClassLoader來嘗試加載;

  4. 若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,若是AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。

下面貼下ClassLoader的loadClass(String name, boolean resolve)源碼:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

代碼很明朗:首先找緩存(findLoadedClass),沒有的話就判斷有沒有parent,有的話就用parent來遞歸的loadClass,然而ExtClassLoader並無設置parent,則會經過findBootstrapClassOrNull來加載class,而findBootstrapClassOrNull則會經過JNI方法」private native Class findBootstrapClass(String name)「來使用BootStrapClassLoader來加載class。

而後若是parent未找到class,則會調用findClass來加載class,findClass是一個protected的空方法,能夠覆蓋它以便自定義class加載過程。

另外,雖然ClassLoader加載類是使用loadClass方法,可是鼓勵用 ClassLoader 的子類重寫 findClass(String),而不是重寫loadClass,這樣就不會覆蓋了類加載默認的雙親委派機制。

雙親委派機制爲何安全

前面談到雙親委派機制是爲了安全而設計的,可是爲何就安全了呢?舉個例子,ClassLoader加載的class文件來源不少,好比編譯器編譯生成的class、或者網絡下載的字節碼。而一些來源的class文件是不可靠的,好比我能夠自定義一個java.lang.Integer類來覆蓋jdk中默認的Integer類,例以下面這樣:

package java.lang;

/**
 * hack
 */
public class Integer {
    public Integer(int value) {
        System.exit(0);
    }
}

初始化這個Integer的構造器是會退出JVM,破壞應用程序的正常進行,若是使用雙親委派機制的話該Integer類永遠不會被調用,覺得委託BootStrapClassLoader加載後會加載JDK中的Integer類而不會加載自定義的這個,能夠看下下面這測試個用例:

        public static void main(String... args) {
        Integer i = new Integer(1);
        System.err.println(i);
    }

執行時JVM並未在new Integer(1)時退出,說明未使用自定義的Integer,因而就保證了安全性。

相關文章
相關標籤/搜索