如java.net.URLClassLoader$3$1 表示URLClassLoader的第三个匿名内部类中的第一个匿名内部类。
示例:
public class Test15 { public static void main(String[] args) { String[] strings = new String[2]; System.out.println(strings.getClass().getClassLoader()); //string[]数组元素类型是String 在rt.jar包中是由根类加载器加载的 System.out.println("----------------"); Test15[] test15s = new Test15[2]; System.out.println(test15s.getClass().getClassLoader());//同理 该元素是由AppClassLoader系统类加载器加载的 但是数组本身不是由类加载器加载 System.out.println("----------------"); int[] ints = new int[2];//如果 元素类型是基本(原生8种)类型,因此数组类没有类装入器。 System.out.println(ints.getClass().getClassLoader()); }}
打印:
/*null 根类加载器----------------sun.misc.Launcher$AppClassLoader@18b4aac2----------------null 为空 */
ClassLoader loade = new NetworkClassLoader(host,port);Object main = loader.loadClass("Main", true).newInstance();
自定义加载器子类必须定义两个方法{@link#findClass findClass}和loadClassData加载类来自网络。一旦它下载了构成类的字节,应该使用方法{@link #defineClass defineClass} to创建一个类实例。一个示例实现是:
class NetworkClassLoader extends ClassLoader { String host; int port; public Class findClass(String name) { byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length);//通过名字将Class对象返回给调用者 } private byte[] loadClassData(String name) { // load the class data from the connection . } }
自定一 此时因为在ClassPath下 所以会调用父类AppClassLoader的系统类加载器 所以自定义的的findClass不会被执行。
public class Test16 extends ClassLoader { private String classLoaderName; private final String fileExtension = ".class"; public Test16(String classLoaderName) { // this(checkCreateClassLoader(), getSystemClassLoader()); // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器 super();//可加可不加 this.classLoaderName = classLoaderName; } public Test16(ClassLoader parent, String classLoaderName) { // this(checkCreateClassLoader(), parent); //ClassLoader中当创建新的类加载器自定义父加载器 如 : //a继承b b继承ClassLoader 此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器 super(parent); this.classLoaderName = classLoaderName; } /** * 查找指定二进制名字的class 这个方法应该被子类加载器实现重写,再检查完对应父加载器之后该方法会被loaderClass()方法调用 , * 在父类中 throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写 * 如: * java.lang.String * * @param className * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { //对应二进制名字对应的字节数组 byte[] data = this.loadClassData(className); System.out.println("findClass invoked" + className); System.out.println("class loader name" + classLoaderName); //defineClass(类名,字节数据,起,末) 创建类实例 return this.defineClass(className, data, 0, data.length); } //获取文件字节数据 private byte[] loadClassData(String name) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null; try { //传过来的文件名加上后缀 is = new FileInputStream(new File(name + this.fileExtension)); baos = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public static void main(String[] args) throws Exception { Test16 test16 = new Test16("test16"); test(test16); } public static void test(ClassLoader classLoader) throws IllegalAccessException, Exception { //改方法会调用我们重写之后的findClass方法 Class<?> clasz = classLoader.loadClass("com.example.demo.com.jvm.Test1"); Object o = clasz.newInstance(); System.out.println(o); }}
打印结果:
//只输出了com.example.demo.com.jvm.Test1@1eb44e46
基于上例重构 新增自定义路径将class字节码路径放在其他位置 此时父类加载器appClassLoader无法加载 此时就会调用自己的findClass() 需要将classpath 下的需要加载的.class删除。
public class Test16 extends ClassLoader { private String classLoaderName; //路径 private String path; private final String fileExtension = ".class"; public Test16(String classLoaderName) { // this(checkCreateClassLoader(), getSystemClassLoader()); // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器 super();//可加可不加 this.classLoaderName = classLoaderName; } public void setPath(String path) { this.path = path; } public Test16(ClassLoader parent, String classLoaderName) { // this(checkCreateClassLoader(), parent); //ClassLoader中当创建新的类加载器自定义父加载器 如 : //a继承b b继承ClassLoader 此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器 super(parent); this.classLoaderName = classLoaderName; } /** * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 , * 在父类中 throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写 * 如: * java.lang.String * * @param className * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { //对应二进制名字对应的字节数组 byte[] data = this.loadClassData(className); System.out.println("findClass invoked" + className); System.out.println("class loader name= " + classLoaderName); //defineClass(类名,字节数据,起,末) 创建类实例 return this.defineClass(className, data, 0, data.length); } //获取文件字节数据 private byte[] loadClassData(String className) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null; className = className.replace(".", "//"); try { //传过来的文件名加上后缀 is = new FileInputStream(new File(this.path + className + this.fileExtension)); baos = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public static void main(String[] args) throws Exception { Test16 test16 = new Test16("test16");// test16.setPath("D://workspaces//zookeeper//target//classes//"); test16.setPath("E://cx//"); //改方法会调用我们重写之后的findClass方法 Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz.hashCode()); Object o = clasz.newInstance();//对象内存地址有哈希值 System.out.println(o); }}
此时findClass中的打印语句执行了 findClass invokedcom.example.demo.com.jvm.Test1class loader name= test16class: 1365202186com.example.demo.com.jvm.Test1@626b2d4a
当我们在编写完自定义类加载器时重写了loadClassData和findClass方法。在main方法中实例化Test16对象调用返回系统类加载器的构造函数,因为在classpath路径以上(双亲委托下并没有找到对应的.class文件 所以自定义加载器去加载 此时调用classLoader的loadClass方法获取对应的Class实例 此时自定义类加载器并没有直接调用findClass方法 而是在loadClass方法中ClassLoader帮我们直接调用了我们自己重写好的findclass方法。
方法只是抛出了一个异常所有我们在自定义类加载器时必须重写对应方法。
当我们调用对应的方法完毕。
重写loadClassData方法将获取对应二进制类名文件字节数组。
在通过方法获取对应二进制名称的Class对象。
而在ClassLoader中的defineClass方法调用了重载的defineClass方法多加了个ProtectionDomainProtectionDomain 类封装域的特征,域中包装一个类集合,在代表给定的主体集合执行这些类的实例时会授予它们一个权限集合。主要是支持支持动态安全策略。
在这个方法里面才是真正获取对应二进制名字的Class对象。
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { //前置处理 protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); //此时调用底层本地C++代码获取Class Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); //后置处理拼接对象后缀名 postDefineClass(c, protectionDomain); return c; }
自此程序运行结束 返回Class对象。
ClassLoader 中loadClass 此时获取的Class还没有链接 只是刚加载到JVM中。
如果类被发现使用上述步骤,和解析标志为真,此方法将调用{@link#resolveClass(Class)}方法的结果类对象。
子类ClassLoader被鼓励重写{@link#findClass(String)},而不是这个方法。
在整个类装入过程中除非被覆盖,否则此方法对的结果进行同步{@link #getClassLoadingLock getClassLoadingLock}方法。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 检查类是否已经加载(一个类只能被加载一次) Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //如果父类不是null 就会使用虚拟机内置的根类加载器去加载二进制名(name对应的数据), //子类ClassLoader被鼓励重写 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(); } } // 如果类被发现使用上述步骤,和解析标志为真,此方法将调用{@link#resolveClass(Class)}方法的结果类对象。 if (resolve) { resolveClass(c); } //返回Class return c; } }
基于上例Test16继续重构。
public class Test16 extends ClassLoader { private String classLoaderName; //路径 private String path; private final String fileExtension = ".class"; public Test16(String classLoaderName) { // this(checkCreateClassLoader(), getSystemClassLoader()); // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器 super();//可加可不加 this.classLoaderName = classLoaderName; } public void setPath(String path) { this.path = path; } public Test16(ClassLoader parent, String classLoaderName) { // this(checkCreateClassLoader(), parent); //ClassLoader中当创建新的类加载器自定义父加载器 如 : //a继承b b继承ClassLoader 此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器 super(parent); this.classLoaderName = classLoaderName; } /** * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 , * 在父类中 throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写 * 如: * java.lang.String * * @param className * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { //对应二进制名字对应的字节数组 byte[] data = this.loadClassData(className); System.out.println("findClass invoked:" + className); System.out.println("class loader name: " + classLoaderName); //defineClass(类名,字节数据,起,末) 创建类实例 return this.defineClass(className, data, 0, data.length); } //获取文件字节数据 private byte[] loadClassData(String className) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null; className = className.replace(".", "//"); try { //传过来的文件名加上后缀 is = new FileInputStream(new File(this.path + className + this.fileExtension)); baos = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public static void main(String[] args) throws Exception { Test16 test16 = new Test16("test16");// test16.setPath("D://workspaces//zookeeper//target//classes//"); test16.setPath("E://cx//"); //改方法会调用我们重写之后的findClass方法 Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz.hashCode()); Object o = clasz.newInstance();//对象内存地址有哈希值 System.out.println(o); Test16 test162 = new Test16("test17"); Class<?> clasz2 = test16.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz2.hashCode()); Object o2 = clasz2.newInstance();//对象内存地址有哈希值 System.out.println(o2); }}
/*当classPath下有对应的加载的.class时 第二次交给父类加载器发现已经加载所以字节拿过来用 所以此时获取的Class类时一致的class: 515132998com.example.demo.com.jvm.Test1@6504e3b2class: 515132998com.example.demo.com.jvm.Test1@515f550a当classPath下没有对应的加载的.class 制定了对应的路径 此时类获取几次就会加载几次 涉及到了命名空间的问题findClass invoked:com.example.demo.com.jvm.Test1class loader name: test16class: 1365202186com.example.demo.com.jvm.Test1@626b2d4a--------------两个不同的命名空间------------------findClass invoked:com.example.demo.com.jvm.Test1class loader name: test17class: 932583850com.example.demo.com.jvm.Test1@cac736f */
总结:同一个命名空间不会出现两个完全相同的类,不同的命名空间会出现两个完全相同的类,父加载器加载的类不可以看到子类加载器加载的类,但是子类加载器加载的类可以看到父类加载器加载的类。
解释:
上例继续改造://将loader1作为loader2的父类加载器。
public class Test16 extends ClassLoader { private String classLoaderName; //路径 private String path; private final String fileExtension = ".class"; public Test16(String classLoaderName) { // this(checkCreateClassLoader(), getSystemClassLoader()); // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器 super();//可加可不加 this.classLoaderName = classLoaderName; } public void setPath(String path) { this.path = path; } public Test16(ClassLoader parent, String classLoaderName) { // this(checkCreateClassLoader(), parent); //ClassLoader中当创建新的类加载器自定义父加载器 如 : //a继承b b继承ClassLoader 此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器 super(parent); this.classLoaderName = classLoaderName; } /** * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 , * 在父类中 throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写 * 如: * java.lang.String * * @param className * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { //对应二进制名字对应的字节数组 byte[] data = this.loadClassData(className); System.out.println("findClass invoked:" + className); System.out.println("class loader name: " + classLoaderName); //defineClass(类名,字节数据,起,末) 创建类实例 return this.defineClass(className, data, 0, data.length); } //获取文件字节数据 private byte[] loadClassData(String className) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null; className = className.replace(".", "//"); try { //传过来的文件名加上后缀 is = new FileInputStream(new File(this.path + className + this.fileExtension)); baos = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public static void main(String[] args) throws Exception { Test16 loader1 = new Test16("loader1");// test16.setPath("D://workspaces//zookeeper//target//classes//"); loader1.setPath("E://cx//"); //改方法会调用我们重写之后的findClass方法 Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz.hashCode()); Object o = clasz.newInstance();//对象内存地址有哈希值 System.out.println(o);// System.out.println("------------两个不同的命名空间--------------------"); Test16 loader2 = new Test16(loader1,"loader2");//将loader1作为loader2的父类加载器 loader2.setPath("E://cx//"); Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz2.hashCode()); Object o2 = clasz2.newInstance();//对象内存地址有哈希值 System.out.println(o2); }}
-------------------------------------------当classPath下没有对应的加载的.class时Test16 loader2 = new Test16(loader1,"loader2");//将loader1作为loader2的父类加载器findClass invoked:com.example.demo.com.jvm.Test1class loader name: loader1class: 1365202186com.example.demo.com.jvm.Test1@626b2d4a //此时父加载器loader1已经加载完毕 loader2直接拿来使用class: 1365202186com.example.demo.com.jvm.Test1@5e91993f
通过上例继续改造: 新增一个类加载器 父类设置为loader2。
public class Test16 extends ClassLoader { private String classLoaderName; //路径 private String path; private final String fileExtension = ".class"; public Test16(String classLoaderName) { // this(checkCreateClassLoader(), getSystemClassLoader()); // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器 super();//可加可不加 this.classLoaderName = classLoaderName; } public void setPath(String path) { this.path = path; } public Test16(ClassLoader parent, String classLoaderName) { // this(checkCreateClassLoader(), parent); //ClassLoader中当创建新的类加载器自定义父加载器 如 : //a继承b b继承ClassLoader 此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器 super(parent); this.classLoaderName = classLoaderName; } /** * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 , * 在父类中 throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写 * 如: * java.lang.String * * @param className * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { //对应二进制名字对应的字节数组 byte[] data = this.loadClassData(className); System.out.println("findClass invoked:" + className); System.out.println("class loader name: " + classLoaderName); //defineClass(类名,字节数据,起,末) 创建类实例 return this.defineClass(className, data, 0, data.length); } //获取文件字节数据 private byte[] loadClassData(String className) { InputStream is = null; byte[] data = null; ByteArrayOutputStream baos = null; className = className.replace(".", "//"); try { //传过来的文件名加上后缀 is = new FileInputStream(new File(this.path + className + this.fileExtension)); baos = new ByteArrayOutputStream(); int ch = 0; while (-1 != (ch = is.read())) { baos.write(ch); } data = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { is.close(); baos.close(); } catch (IOException e) { e.printStackTrace(); } } return data; } public static void main(String[] args) throws Exception { Test16 loader1 = new Test16("loader1");// test16.setPath("D://workspaces//zookeeper//target//classes//"); loader1.setPath("E://cx//"); //改方法会调用我们重写之后的findClass方法 Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz.hashCode()); Object o = clasz.newInstance();//对象内存地址有哈希值 System.out.println(o); System.out.println();// System.out.println("------------两个不同的命名空间--------------------"); Test16 loader2 = new Test16(loader1, "loader2");//将loader1作为loader2的父类加载器 loader2.setPath("E://cx//"); Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz2.hashCode()); Object o2 = clasz2.newInstance();//对象内存地址有哈希值 System.out.println(o2); System.out.println(); Test16 loader3 = new Test16(loader2,"loader3"); loader3.setPath("E://cx//"); //改方法会调用我们重写之后的findClass方法 Class<?> clasz3 = loader3.loadClass("com.example.demo.com.jvm.Test1"); System.out.println("class: " + clasz3.hashCode()); Object o3 = clasz3.newInstance();//对象内存地址有哈希值 System.out.println(o3); }}
命名空间一致。
命名空间一致findClass invoked:com.example.demo.com.jvm.Test1class loader name: loader1class: 1365202186com.example.demo.com.jvm.Test1@626b2d4a loader1 先去加类加载class: 1365202186com.example.demo.com.jvm.Test1@5e91993f loader2 交给父类父类交给appClassLoader加载发现已经加载直接拿来用class: 1365202186com.example.demo.com.jvm.Test1@1c4af82c loader3 同上
本文链接:http://www.28at.com/showinfo-26-82180-0.htmlJVM类加载:如何手写自定义类加载器,命名空间详解
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 超级离谱的前端需求:搜索图片里的文字