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

  • 相关阅读:
    “键鼠耕耘,IT家园”,博客园2010T恤正式发布
    解决jQuery冲突问题
    上周热点回顾(5.316.6)
    博客园电子期刊2010年5月刊发布啦
    上周热点回顾(6.76.13)
    Chrome/5.0.375.70 处理 <pre></pre> 的 Bug
    [转]C# MemoryStream和BinaryFormatter
    [转]Android adb不是内部或外部命令 问题解决
    [转]HttpWebRequest解析 作用 介绍
    财富中文网 2010年世界500强排行榜(企业名单)
  • 原文地址:https://www.cnblogs.com/java-bible/p/14474051.html
Copyright © 2011-2022 走看看