ClassLoader是Java的类加载器,用于把class文件加载到JVM中,下面大概了解一下Java类加载器的概况。java
Java提供了三个ClassLoader:bootstrap
用于加载JAVA核心类库,也就是环境变量的%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar等。数组
在JVM启动时加入-Xbootclasspath参数,能够把对应路径也加载到Bootstrap的路径列表中来,这个参数有两种用法:缓存
1),-Xbootclasspath/a:{人工指定路径},把对应路径加载到Bootstrap默认路径后面,也就是说,若是有class重复,以Bootstrap默认路径下的类为准(由于是按照路径列表顺序加载的),举例:app
java -Xbootclasspath/a:D:\test\Test.jardom
2),-Xbootclasspath/p: {人工指定路径},把对应路径加载到Bootstrap默认路径后面,也就是说,若是有class重复,以指定路径下的类为准,举例:ide
java -Xbootclasspath/p:D:\test\Test.jar测试
扩展类加载器,加载环境变量%JRE_HOME%\lib\ext目录下的class文件this
这个加载器也能够在JVM启动时使用参数改变加载的行为,参数是-D java.ext.dirs=,做用是替换Java扩展类加载器所加载的文件目录。url
注意,该参数是替换而不是追加,由于这个加载器的加载路径只有一个,也就是说,%JRE_HOME%\lib\ext是扩展类加载器的默认路径,若是咱们在启动时使用-Djava.ext.dirs=d:/test,那么java就再也不加载%JRE_HOME%\lib\ext路径下的文件。
加载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包。
java的加载器在查找或加载class时,须要确认这个class是否已经被加载了,若是已经被加载了本身就再也不重复加载。
类加载器查找class的方式叫作双亲委托模式,基本方法是:
1,本身先查缓存,验证类是否已加载,若是缓存中没有则向上委托父加载器查询。
2,父加载器接到委托也是查本身的缓存,若是没有再向上委托。
3,直到最顶级的BootstrapClassLoader也没在缓存中找到该类,则Bootstrap ClassLoader从他本身的加载路径中查找该类,若是找不到则返回下一级加载器。
4,下一级加载器也从他本身的加载路径中查找该类,若是找不到则返回下一级加载器,直到返回最开始的加载器。
简单来讲,就是从下往上查缓存,而后从上往下扫描路径。若是在其中任何一步发现已经加载了该类,都会马上返回,再也不进行后面的查找。
画个图来表示这个流程的话,应该是这样的:
图1
查找的顺序就是图上从①到⑥
自定义的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()方法加载。
首先是基础知识:
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宝典