主頁 > 後端開發 > Java的類加載器(ClassLoader)簡介

Java的類加載器(ClassLoader)簡介

2021-03-03 18:29:43 後端開發

ClassLoader是Java的類加載器,用于把class檔案加載到JVM中,下面大概了解一下Java類加載器的概況,

一,java提供的加載器

Java提供了三個ClassLoader:

1,BootstrapClassLoader

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

在JVM啟動時加入-Xbootclasspath引數,可以把對應路徑也加載到Bootstrap的路徑串列中來,這個引數有兩種用法:

1),-Xbootclasspath/a:{人工指定路徑},把對應路徑加載到Bootstrap默認路徑后面,也就是說,如果有class重復,以Bootstrap默認路徑下的類為準(因為是按照路徑串列順序加載的),舉例:

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

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

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

2,Extention ClassLoader

擴展類加載器,加載環境變數%JRE_HOME%\lib\ext目錄下的class檔案

這個加載器也可以在JVM啟動時使用引數改變加載的行為,引數是-D java.ext.dirs=,作用是替換Java擴展類加載器所加載的檔案目錄,

注意,該引數是替換而不是追加,因為這個加載器的加載路徑只有一個,也就是說,%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 = https://www.cnblogs.com/java-bible/archive/2021/03/03/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 = https://www.cnblogs.com/java-bible/archive/2021/03/03/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

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/265564.html

標籤:其他

上一篇:C語言學習:用C語言實作簡單的計算器

下一篇:【PyCharm中文教程 02】PyCharm 社區版下載與安裝

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more