zoukankan      html  css  js  c++  java
  • Java的类加载器(ClassLoader)简介

    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: estTest.jar

    2),-Xbootclasspath/p: {人工指定路径},把对应路径加载到Bootstrap默认路径后面,也就是说,如果有class重复,以指定路径下的类为准,举例:

    java -Xbootclasspath/p:D: estTest.jar

    2,Extention ClassLoader

    扩展类加载器,加载环境变量%JRE_HOME%libext目录下的class文件

    这个加载器也可以在JVM启动时使用参数改变加载的行为,参数是-D java.ext.dirs=,作用是替换Java扩展类加载器所加载的文件目录。

    注意,该参数是替换而不是追加,因为这个加载器的加载路径只有一个,也就是说,%JRE_HOME%libext是扩展类加载器的默认路径,如果我们在启动时使用-Djava.ext.dirs=d:/test,那么java就不再加载%JRE_HOME%libext路径下的文件。

    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:ProgramFilesJavajre7lib esources.jar;

    C:Program FilesJavajre7lib t.jar;

    C:ProgramFilesJavajre7libsunrsasign.jar;

    C:Program FilesJavajre7libjsse.jar;

    C:Program FilesJavajre7libjce.jar;

    C:ProgramFilesJavajre7libcharsets.jar;

    C:Program FilesJavajre7libjfr.jar;

    C:Program FilesJavajre7classes

    实际输出结果是没有换行的,我在分号处加了换行。

    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 FilesJavajre7libext;

    C:WindowsSunJavalibext

    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 estin;

    C:UserslkDownloadsasm-4.2.jar;

    C:UserslkDesktopdubbo-2.8.3.2.jar;

    C:UserslkDownloadscglib-2.2.jar;

    C:UserslkDownloads etty-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

  • 相关阅读:
    泛微云桥e-Bridge 目录遍历,任意文件读取
    (CVE-2020-8209)XenMobile-控制台存在任意文件读取漏洞
    selenium 使用初
    将HTML文件转换为MD文件
    Python对word文档进行操作
    使用java安装jar包出错,提示不是有效的JDK java主目录
    Windows server 2012安装VM tools异常解决办法
    ifconfig 命令,改变主机名,改DNS hosts、关闭selinux firewalld netfilter 、防火墙iptables规则
    iostat iotop 查看硬盘的读写、 free 查看内存的命令 、netstat 命令查看网络、tcpdump 命令
    使用w uptime vmstat top sar nload 等命令查看系统负载
  • 原文地址:https://www.cnblogs.com/java-bible/p/14474051.html
Copyright © 2011-2022 走看看