Java的類加載器(ClassLoader)簡介

ClassLoader是Java的類加載器,用於把class文件加載到JVM中,下面大概瞭解一下Java類加載器的概況。java

一,java提供的加載器

Java提供了三個ClassLoader:bootstrap

1,BootstrapClassLoader

用於加載JAVA核心類庫,也就是環境變量的%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar等。數組

在JVM啓動時加入-Xbootclasspath參數,能夠把對應路徑也加載到Bootstrap的路徑列表中來,這個參數有兩種用法:緩存

1),-Xbootclasspath/a:{人工指定路徑},把對應路徑加載到Bootstrap默認路徑後面,也就是說,若是有class重複,以Bootstrap默認路徑下的類爲準(由於是按照路徑列表順序加載的),舉例:app

java -Xbootclasspath/a:D:\test\Test.jardom

2),-Xbootclasspath/p: {人工指定路徑},把對應路徑加載到Bootstrap默認路徑後面,也就是說,若是有class重複,以指定路徑下的類爲準,舉例:ide

java -Xbootclasspath/p:D:\test\Test.jar測試

2,Extention ClassLoader

擴展類加載器,加載環境變量%JRE_HOME%\lib\ext目錄下的class文件this

這個加載器也能夠在JVM啓動時使用參數改變加載的行爲,參數是-D java.ext.dirs=,做用是替換Java擴展類加載器所加載的文件目錄。url

注意,該參數是替換而不是追加,由於這個加載器的加載路徑只有一個,也就是說,%JRE_HOME%\lib\ext是擴展類加載器的默認路徑,若是咱們在啓動時使用-Djava.ext.dirs=d:/test,那麼java就再也不加載%JRE_HOME%\lib\ext路徑下的文件。

3,AppclassLoader

加載classpath中的class類,經過在JVM啓動命令中的-classpath參數指定路徑,能夠指定絕對路徑、相對路徑、環境變量等,舉例:

java –classpath %CLASSPATH%

二,各類加載器之間的關係

從加載關係來講:

1,BootstrapClassLoader是Extention ClassLoader的父加載器。

2,ExtentionClassLoader是AppclassLoader的父加載器。

注意,這裏的父加載器並非java語言裏的父類,只是邏輯上的。

從Java語言的角度來講:

1,ExtentionClassLoader對應的java類是ExtClassLoader,他的父類是java.net.URLClassLoader。

2,AppclassLoader對應的java類是AppClassLoader,他的父類也是java.net.URLClassLoader,沒錯,和ExtClassLoader同樣。

3,BootstrapClassLoader是C++編寫的,壓根沒有對應的java類,固然也成不了別人的父類。

ClassLoader類有getParent()方法,能夠獲得父加載器,一個加載器的父加載器是在他初始化的時候指定的。

AppclassLoader用getParent()方法獲得的是ExtClassLoader。

ExtClassLoader用getParent()方法獲得的是null。

若是咱們自定義一個加載器,每每要繼承ClassLoader類,此時默認的父加載器是AppClassLoader。

三,加載器的加載順序

加載器在JVM啓動時的加載順序是:

1,BootstrapClassLoader

2,ExtentionClassLoader

3,AppclassLoader

關於這個加載順序能夠參考sun.misc.Launcher類,這個類在JVM啓動時初始化了各個加載器,代碼以下:

/**

 *This class is used by the system to launch the main application.

Launcher */

public class Launcher {

   private static URLStreamHandlerFactory factory = new Factory();

   private static Launcher launcher = new Launcher();

   private static String bootClassPath =

       System.getProperty("sun.boot.class.path");

 

   public static Launcher getLauncher() {

       return launcher;

    }

 

   private ClassLoader loader;

 

   public Launcher() {

       // Create the extension class loader

       ClassLoader extcl;

       try {

           extcl = ExtClassLoader.getExtClassLoader();

       } catch (IOException e) {

           throw new InternalError(

                "Could not createextension class loader", e);

       }

 

       // Now create the class loader to use to launch the application

       try {

           loader = AppClassLoader.getAppClassLoader(extcl);

       } catch (IOException e) {

           throw new InternalError(

                "Could not create applicationclass loader", e);

       }

 

       // Also set the context class loader for the primordial thread.

       Thread.currentThread().setContextClassLoader(loader);

 

       // Finally, install a security manager if requested

       String s = System.getProperty("java.security.manager");

       if (s != null) {

           SecurityManager sm = null;

           if ("".equals(s) || "default".equals(s)) {

                sm = newjava.lang.SecurityManager();

           } else {

                try {

                    sm =(SecurityManager)loader.loadClass(s).newInstance();

                } catch (IllegalAccessExceptione) {

                } catch (InstantiationExceptione) {

                } catch (ClassNotFoundExceptione) {

                } catch (ClassCastException e){

                }

           }

           if (sm != null) {

                System.setSecurityManager(sm);

           } else {

                throw new InternalError(

                    "Could not createSecurityManager: " + s);

           }

       }

}

……後面還有不少

}

能夠看到,在Launcher的無參構造中,先是初始化了ExtClassLoader,而後初始化AppClassLoader,其實Bootstrap ClassLoader在這以前就加載完了,類中有這樣一個屬性:

private static String bootClassPath =

       System.getProperty("sun.boot.class.path");

這個就是Bootstrap ClassLoader類加載的路徑,能夠本身寫一段代碼看看這個路徑是什麼:

System.out.println(System.getProperty("sun.boot.class.path"));

輸出結果:

C:\ProgramFiles\Java\jre7\lib\resources.jar;

C:\Program Files\Java\jre7\lib\rt.jar;

C:\ProgramFiles\Java\jre7\lib\sunrsasign.jar;

C:\Program Files\Java\jre7\lib\jsse.jar;

C:\Program Files\Java\jre7\lib\jce.jar;

C:\ProgramFiles\Java\jre7\lib\charsets.jar;

C:\Program Files\Java\jre7\lib\jfr.jar;

C:\Program Files\Java\jre7\classes

實際輸出結果是沒有換行的,我在分號處加了換行。

Launcher類中加載的ExtClassLoader和AppClassLoader這兩個類的定義也是在Launcher中,源碼也能夠看一下

ExtClassLoader類的定義:

/*
     * The class loader used for loading installed extensions.
     */
    static class ExtClassLoader extends URLClassLoader {

        static {
            ClassLoader.registerAsParallelCapable();
        }

        /**
         * create an ExtClassLoader. The ExtClassLoader is created
         * within a context that limits which files it can read
         */
        public static ExtClassLoader getExtClassLoader() throws IOException
        {
            final File[] dirs = getExtDirs();

            try {
                // Prior implementations of this doPrivileged() block supplied
                // aa synthesized ACC via a call to the private method
                // ExtClassLoader.getContext().

                return AccessController.doPrivileged(
                    new PrivilegedExceptionAction<ExtClassLoader>() {
                        public ExtClassLoader run() throws IOException {
                            int len = dirs.length;
                            for (int i = 0; i < len; i++) {
                                MetaIndex.registerDirectory(dirs[i]);
                            }
                            return new ExtClassLoader(dirs);
                        }
                    });
            } catch (java.security.PrivilegedActionException e) {
                throw (IOException) e.getException();
            }
        }

        void addExtURL(URL url) {
            super.addURL(url);
        }

        /*
         * Creates a new ExtClassLoader for the specified directories.
         */
        public ExtClassLoader(File[] dirs) throws IOException {
            super(getExtURLs(dirs), null, factory);
            SharedSecrets.getJavaNetAccess().
                getURLClassPath(this).initLookupCache(this);
        }

        private static File[] getExtDirs() {
            String s = System.getProperty("java.ext.dirs");
            File[] dirs;
            if (s != null) {
                StringTokenizer st =
                    new StringTokenizer(s, File.pathSeparator);
                int count = st.countTokens();
                dirs = new File[count];
                for (int i = 0; i < count; i++) {
                    dirs[i] = new File(st.nextToken());
                }
            } else {
                dirs = new File[0];
            }
            return dirs;
        }

        private static URL[] getExtURLs(File[] dirs) throws IOException {
            Vector<URL> urls = new Vector<URL>();
            for (int i = 0; i < dirs.length; i++) {
                String[] files = dirs[i].list();
                if (files != null) {
                    for (int j = 0; j < files.length; j++) {
                        if (!files[j].equals("meta-index")) {
                            File f = new File(dirs[i], files[j]);
                            urls.add(getFileURL(f));
                        }
                    }
                }
            }
            URL[] ua = new URL[urls.size()];
            urls.copyInto(ua);
            return ua;
        }

        /*
         * Searches the installed extension directories for the specified
         * library name. For each extension directory, we first look for
         * the native library in the subdirectory whose name is the value
         * of the system property <code>os.arch</code>. Failing that, we
         * look in the extension directory itself.
         */
        public String findLibrary(String name) {
            name = System.mapLibraryName(name);
            URL[] urls = super.getURLs();
            File prevDir = null;
            for (int i = 0; i < urls.length; i++) {
                // Get the ext directory from the URL
                File dir = new File(urls[i].getPath()).getParentFile();
                if (dir != null && !dir.equals(prevDir)) {
                    // Look in architecture-specific subdirectory first
                    // Read from the saved system properties to avoid deadlock
                    String arch = VM.getSavedProperty("os.arch");
                    if (arch != null) {
                        File file = new File(new File(dir, arch), name);
                        if (file.exists()) {
                            return file.getAbsolutePath();
                        }
                    }
                    // Then check the extension directory
                    File file = new File(dir, name);
                    if (file.exists()) {
                        return file.getAbsolutePath();
                    }
                }
                prevDir = dir;
            }
            return null;
        }

        private static AccessControlContext getContext(File[] dirs)
            throws IOException
        {
            PathPermissions perms =
                new PathPermissions(dirs);

            ProtectionDomain domain = new ProtectionDomain(
                new CodeSource(perms.getCodeBase(),
                    (java.security.cert.Certificate[]) null),
                perms);

            AccessControlContext acc =
                new AccessControlContext(new ProtectionDomain[] { domain });

            return acc;
        }
    }

能夠看到裏面的getExtDirs()方法中,得到了java.ext.dirs參數的內容,這個地址也能夠打印出來看看:

System.out.println(System.getProperty("java.ext.dirs"));

輸出的結果:

C:\Program Files\Java\jre7\lib\ext;

C:\Windows\Sun\Java\lib\ext

AppClassLoader類的定義:

/**
     * The class loader used for loading from java.class.path.
     * runs in a restricted security context.
     */
    static class AppClassLoader extends URLClassLoader {

        static {
            ClassLoader.registerAsParallelCapable();
        }

        public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException
        {
            final String s = System.getProperty("java.class.path");
            final File[] path = (s == null) ? new File[0] : getClassPath(s);

            // Note: on bugid 4256530
            // Prior implementations of this doPrivileged() block supplied
            // a rather restrictive ACC via a call to the private method
            // AppClassLoader.getContext(). This proved overly restrictive
            // when loading  classes. Specifically it prevent
            // accessClassInPackage.sun.* grants from being honored.
            //
            return AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }

        final URLClassPath ucp;

        /*
         * Creates a new AppClassLoader
         */
        AppClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent, factory);
            ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
            ucp.initLookupCache(this);
        }

        /**
         * Override loadClass so we can checkPackageAccess.
         */
        public Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            int i = name.lastIndexOf('.');
            if (i != -1) {
                SecurityManager sm = System.getSecurityManager();
                if (sm != null) {
                    sm.checkPackageAccess(name.substring(0, i));
                }
            }

            if (ucp.knownToNotExist(name)) {
                // The class of the given name is not found in the parent
                // class loader as well as its local URLClassPath.
                // Check if this class has already been defined dynamically;
                // if so, return the loaded class; otherwise, skip the parent
                // delegation and findClass.
                Class<?> c = findLoadedClass(name);
                if (c != null) {
                    if (resolve) {
                        resolveClass(c);
                    }
                    return c;
                }
                throw new ClassNotFoundException(name);
            }

            return (super.loadClass(name, resolve));
        }

        /**
         * allow any classes loaded from classpath to exit the VM.
         */
        protected PermissionCollection getPermissions(CodeSource codesource)
        {
            PermissionCollection perms = super.getPermissions(codesource);
            perms.add(new RuntimePermission("exitVM"));
            return perms;
        }

        /**
         * This class loader supports dynamic additions to the class path
         * at runtime.
         *
         * @see java.lang.instrument.Instrumentation#appendToSystemClassPathSearch
         */
        private void appendToClassPathForInstrumentation(String path) {
            assert(Thread.holdsLock(this));

            // addURL is a no-op if path already contains the URL
            super.addURL( getFileURL(new File(path)) );
        }

        /**
         * create a context that can read any directories (recursively)
         * mentioned in the class path. In the case of a jar, it has to
         * be the directory containing the jar, not just the jar, as jar
         * files might refer to other jar files.
         */

        private static AccessControlContext getContext(File[] cp)
            throws java.net.MalformedURLException
        {
            PathPermissions perms =
                new PathPermissions(cp);

            ProtectionDomain domain =
                new ProtectionDomain(new CodeSource(perms.getCodeBase(),
                    (java.security.cert.Certificate[]) null),
                perms);

            AccessControlContext acc =
                new AccessControlContext(new ProtectionDomain[] { domain });

            return acc;
        }
    }

這個類的getAppClassLoader()方法中,得到了java.class.path參數,能夠打印出來:

System.out.println(System.getProperty("java.class.path"));

輸出結果:

D:\workspace\test\bin;

C:\Users\lk\Downloads\asm-4.2.jar;

C:\Users\lk\Desktop\dubbo-2.8.3.2.jar;

C:\Users\lk\Downloads\cglib-2.2.jar;

C:\Users\lk\Downloads\netty-3.2.5.Final.jar

都是我在classpath裏面配的目錄和jar包。

四,查找class和雙親委託

java的加載器在查找或加載class時,須要確認這個class是否已經被加載了,若是已經被加載了本身就再也不重複加載。

類加載器查找class的方式叫作雙親委託模式,基本方法是:

1,本身先查緩存,驗證類是否已加載,若是緩存中沒有則向上委託父加載器查詢。

2,父加載器接到委託也是查本身的緩存,若是沒有再向上委託。

3,直到最頂級的BootstrapClassLoader也沒在緩存中找到該類,則Bootstrap ClassLoader從他本身的加載路徑中查找該類,若是找不到則返回下一級加載器。

4,下一級加載器也從他本身的加載路徑中查找該類,若是找不到則返回下一級加載器,直到返回最開始的加載器。

簡單來講,就是從下往上查緩存,而後從上往下掃描路徑。若是在其中任何一步發現已經加載了該類,都會馬上返回,再也不進行後面的查找。

畫個圖來表示這個流程的話,應該是這樣的:

圖1

ThirdPartyImage_f5d0de06.png

查找的順序就是圖上從①到⑥

五,自定義ClassLoader

自定義的ClassLoader類,通常須要知足如下條件:

1,繼承java.lang.ClassLoader類,或者繼承他的子類好比java.net.URLClassLoader。

2,重寫findClass()方法或者重寫loadClass()方法,findClass()會在調用加載器的loadClass()方法時調用。

3,在findClass()中使用defineClass()方法,這個方法不須要本身實現,是父類ClassLoader的方法。這個方法的參數在後面的例子中再詳解。

使用自定義的ClassLoader類,通常須要如下步驟:

1,初始化ClassLoader。

2,調用ClassLoader的loadClass()方法加載目標class,參數是String類型,內容包括目標類的包名和類名,不包括」.class」,這個方法是父類ClassLoader的方法,不須要本身定義。在調用這個方法時,就會調用本身在加載器中寫的findClass()方法。

3,使用加載好的類的class.newInstance()就能夠獲得目標類的對象。

下面舉個例子

首先是目標Class,簡單寫一個:

package classloader;

public class Test {

    public void hello() {
        System.out.println("hello world");
    }

}

下面是重點,自定義ClassLoader:

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {

    private String myClassPath;

    public MyClassLoader(String path) {
        myClassPath = path;
    }

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

        File file = new File(myClassPath, name+".class");

        try {
            FileInputStream is = new FileInputStream(file);

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len = 0;
            try {
                while ((len = is.read()) != -1) {
                    bos.write(len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            byte[] data = bos.toByteArray();
            is.close();
            bos.close();

            return defineClass(name, data, 0, data.length, null);

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return super.findClass(name);
    }

}

自定義的ClassLoader能夠繼承ClassLoader類,並重寫findClass(String name)方法,這個方法中的內容,基本就是爲了最後的調用defineClass()方法作準備。

defineClass()方法在父類中有多個重載的方法,他們最終調用的是一個5個參數的defineClass()方法,這5個參數分別是:

1,文件名(帶」.class」)

2,class文件內容的二進制數組

3,二進制數組中表示class數據開始的下標

4,class二進制數據的長度

5,protectionDomain,標識了類的封裝域的權限信息,能夠沒有(本例就沒有),詳解可參考JDK文檔。

從這個方法的定義來看,彷佛是能夠支持一長串二進制數組,開發者只須要指定數組中表明目標Class的開始下標和長度,java就能夠從中截取出目標Class的信息並裝載,但我沒想到有什麼場景能夠用到這個設定。(本例中二進制數組來源於完整的class文件,因此開始下標是0,而且java須要讀取整個數組)

最後寫一個類使用一下咱們自定義的加載器:

package classloader;

import java.lang.reflect.Method;

public class ClassLoaderTest {

    public static void main(String[] args) {

        try {

            // 初始化加載器
            MyClassLoader myLoader = new MyClassLoader("D:\\workspace\\test\\bin");

            // 加載class
            Class c = myLoader.loadClass("classloader.Test");

            // 驗證
            Object obj = c.newInstance();
            Method method = c.getDeclaredMethod("hello", null);
            method.invoke(obj, null);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

運行這個類,控制檯輸出hello world,說明目標類加載成功。

loadClass()方法在是在ClassLoader類中定義的,方法代碼以下:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                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.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

能夠看到,方法首先檢查這個類有沒有被加載,若是沒有被加載,則先去父加載器加載。若是沒有父加載器,則使用Bootstrap ClassLoader加載。若是父加載器沒能加載這個類,則調用findClass()方法加載。

六,從新加載class,熱替換

首先是基礎知識:

1,java目前沒有專門的API,用來卸載JVM中加載的類。

2,要卸載JVM中的類,須要該類的對象都被回收,加載該類的ClassLoader也被回收,使用該類的線程結束等條件,比較嚴格。

3,在java中,不一樣的ClassLoader能夠加載同一個類,即便class文件是同一個也能夠被加載。可是同一個ClassLoader不能重複加載一個類,重複加載會報錯。

總的來講,在不停服務的狀況下熱替換class不是很靠譜,如今的java版本也根本沒打算讓開發者這麼作。

雖然能夠經過新建ClassLoader實例的方法來改變新加載的Class內容,但以前ClassLoader加載的類和對象不會被修改,何時能被GC回收也很不可控,玩玩能夠,線上環境慎用,後續的坑不少。

仍是老老實實重啓比較靠譜。

不過咱們依然能夠作出一個山寨版的熱替換功能,方案就是以前提到的新建ClassLoader實例。

首先我準備了兩個目標類,分別編譯成class文件

第一個:

package classloader;

public class Test {

    public void hello() {
        System.out.println("hello world");
//        System.out.println("Are you OK");
        
    }

}

第二個:

package classloader;

public class Test {

    public void hello() {
//        System.out.println("hello world");
        System.out.println("Are you OK");
        
    }

}

我把第二個類編譯出的class文件單獨保存,未來要替換第一個class。

而後是本身定義的ClassLoader類:

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class MyClassLoader2 extends ClassLoader {

    private String myClassPath;
    
    public MyClassLoader2(String path) {
        myClassPath = path;
    }

    public Class<?> loadMyClass(String name){
        
        System.out.println("從新加載:"+name);
        
        File file = new File(myClassPath+File.separator+name.substring(0,name.indexOf(".")), name.substring(name.indexOf(".")+1,name.length())+".class");
        if(!file.exists()){
            return null;
        }
        
        try {
            
            FileInputStream is = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len = 0;
            try {
                while ((len = is.read()) != -1) {
                    bos.write(len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            byte[] data = bos.toByteArray();
            is.close();
            bos.close();

            return defineClass(name, data, 0, data.length, null);

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return null;
        
    }
    
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("loadClass():"+name);
        Class<?> cls = null;
        if (cls == null){
            cls=loadMyClass(name);
        }
        if(cls==null){
            cls = getSystemClassLoader().loadClass(name); 
            System.out.println("getSystemClassLoader():"+ getSystemClassLoader());
        }
        if (cls == null){
            throw new ClassNotFoundException(name);  
        }
        return cls;  
    }

}

這個自定義的加載器重寫了ClassLoader類的loadClass()方法。注意,對於目標路徑下的類,每次都會從新加載,沒有判斷重複。

在classLoader()方法中先過本身的加載器,本身的加載器必須在特定目錄中存在class文件才能夠加載,不然就用系統定義的加載器加載,由於重寫loaderClass()方法以後,目標類全部的相關類也會用這個方法加載(好比目標類的父類java.lang.Object)。

最後是測試類:

package classloader;

import java.lang.reflect.Method;

public class ClassLoaderTest {

    public static void main(String[] args) {
        
//        loadClass();
        loadClass2();
        
    }
    
    public static void loadClass(){
        
        try {
            // 初始化加載器
            MyClassLoader myLoader = new MyClassLoader("D:\\workspace\\test\\bin");

            // 加載class
            Class c = myLoader.loadClass("classloader.Test");

            // 驗證
            Object obj = c.newInstance();
            Method method = c.getDeclaredMethod("hello", null);
            method.invoke(obj, null);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void loadClass2(){
        
        try {
            
            while(true){
                
                // 初始化加載器
                MyClassLoader2 myLoader = new MyClassLoader2("D:\\workspace\\test\\bin");
                
                // 加載class
                Class c = myLoader.loadClass("classloader.Test");
                
                System.out.println(c.getClassLoader());
                System.out.println(Class.forName("classloader.Test").getClassLoader().toString());
                System.out.println();
                
                // 驗證
                Object obj = c.newInstance();
                Method method = c.getDeclaredMethod("hello", null);
                method.invoke(obj, null);
                
                Thread.sleep(1000);
            }
            
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

測試類其實是每隔一秒鐘新建一個ClassLoader的實例,並用新ClassLoader加載目標類。

在程序啓動以前,編譯路徑下是第一個目標類的Class文件(hello world),在程序啓動以後把第二個Class文件(Are you OK)替換第一個,新加載的目標類就能夠調用第二個目標類的方法。

運行後輸出的結果以下:

loadClass():classloader.Test

從新加載:classloader.Test

loadClass():java.lang.Object

從新加載:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@616affac

sun.misc.Launcher$AppClassLoader@1ddd40f3

 

loadClass():java.lang.System

從新加載:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

從新加載:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

hello world

loadClass():classloader.Test

從新加載:classloader.Test

loadClass():java.lang.Object

從新加載:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@170a6001

sun.misc.Launcher$AppClassLoader@1ddd40f3

 

loadClass():java.lang.System

從新加載:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

從新加載:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

Are you OK

loadClass():classloader.Test

從新加載:classloader.Test

loadClass():java.lang.Object

從新加載:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@6ef82fe7

sun.misc.Launcher$AppClassLoader@1ddd40f3

 

loadClass():java.lang.System

從新加載:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

從新加載:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

Are you OK

loadClass():classloader.Test

從新加載:classloader.Test

loadClass():java.lang.Object

從新加載:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@28a2f6b

sun.misc.Launcher$AppClassLoader@1ddd40f3

 

loadClass():java.lang.System

從新加載:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

loadClass():java.io.PrintStream

從新加載:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

Are you OK

loadClass():classloader.Test

從新加載:classloader.Test

loadClass():java.lang.Object

從新加載:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@1ddd40f3

classloader.MyClassLoader2@6665e41

sun.misc.Launcher$AppClassLoader@1ddd40f3

從輸出的結果能夠看到,重寫的loadClass()方法不但須要加載目標Test類,還要加載java.lang.Object,java.lang.System等類。

經過loadClass()方法獲得的Class,調用class.getClassLoader()方法獲得的加載器,就是本身定義的MyClassLoader2,每次的實例都不同,而經過Class.forName().getClassLoader()方法獲得的加載器,是AppClassLoader,每次的實例都同樣。

另外,若是在測試類中只使用一個ClassLoader的實例,在循環中屢次加載目標類,則會報錯,代碼是這樣:

package classloader;

import java.lang.reflect.Method;

public class ClassLoaderTest {

    public static void main(String[] args) {
        
//        loadClass();
        loadClass2();
        
    }
    
    public static void loadClass(){
        
        try {
            // 初始化加載器
            MyClassLoader myLoader = new MyClassLoader("D:\\workspace\\test\\bin");

            // 加載class
            Class c = myLoader.loadClass("classloader.Test");

            // 驗證
            Object obj = c.newInstance();
            Method method = c.getDeclaredMethod("hello", null);
            method.invoke(obj, null);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void loadClass2(){
        
        try {
            
            // 初始化加載器
            MyClassLoader2 myLoader = new MyClassLoader2("D:\\workspace\\test\\bin");
            
            while(true){
                
                // 加載class
                Class c = myLoader.loadClass("classloader.Test");
                
                System.out.println(c.getClassLoader());
                System.out.println(Class.forName("classloader.Test").getClassLoader().toString());
                System.out.println();
                
                // 驗證
                Object obj = c.newInstance();
                Method method = c.getDeclaredMethod("hello", null);
                method.invoke(obj, null);
                
                Thread.sleep(1000);
            }
            
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

程序啓動後的輸出是這樣:

loadClass():classloader.Test

從新加載:classloader.Test

loadClass():java.lang.Object

從新加載:java.lang.Object

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@28d320d6

classloader.MyClassLoader2@37b7a72b

sun.misc.Launcher$AppClassLoader@28d320d6

 

loadClass():java.lang.System

從新加載:java.lang.System

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@28d320d6

loadClass():java.io.PrintStream

從新加載:java.io.PrintStream

getSystemClassLoader():sun.misc.Launcher$AppClassLoader@28d320d6

Are you OK

loadClass():classloader.Test

從新加載:classloader.Test

Exception in thread "main"java.lang.LinkageError: loader (instance of classloader/MyClassLoader2): attempted duplicate class definition for name: "classloader/Test"

         atjava.lang.ClassLoader.defineClass1(Native Method)

         atjava.lang.ClassLoader.defineClass(Unknown Source)

         atclassloader.MyClassLoader2.loadMyClass(MyClassLoader2.java:42)

         atclassloader.MyClassLoader2.loadClass(MyClassLoader2.java:58)

         atclassloader.ClassLoaderTest.loadClass2(ClassLoaderTest.java:43)

         atclassloader.ClassLoaderTest.main(ClassLoaderTest.java:10)

也就是說,第二次加載Test類時報錯。

關注公衆號:java寶典

a

相關文章
相關標籤/搜索