JVM 阅读JDK中ClassLoader注释,以及自定义类加载器

您所在的位置:网站首页 加载中的英文 JVM 阅读JDK中ClassLoader注释,以及自定义类加载器

JVM 阅读JDK中ClassLoader注释,以及自定义类加载器

2024-04-19 06:13| 来源: 网络整理| 查看: 265

前言 学习就应该阅读官方写JavaDoc,JavaDoc是第一手资料,应该是准确无疑的。 本篇以翻译ClassLoader类中文档注释为主,如有不通顺,请自行看英文注释。 英文版

image.png

翻译 类加载器是负责加载类的对象。 ClassLoader 类是一个抽象类。 给定类的二进制名称(binary name),类加载器应该尝试定位或生成(locate or generate)构成类定义的数据。 典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

批注:

给定类的名称,类加载器会找到定义对应类的数据. binary name: 其实就是字符串 "java.lang.String" "javax.swing.JSpinner$DefaultEditor"( $ 后面跟着是内部类的类名) "java.security.KeyStore$Builder$FileBuilder$1"( $1 类中的第一个匿名内部类) "java.net.URLClassLoader$3$1"(URLClassLoader的第3个匿名内部类中第一个匿名内部类) 定位或生成(locate or generate): 说定位是因为类存在磁盘的某个地方等待被加载,说生成是因为有些类是动态生成的,如动态代理。 每个Class对象都包含定义它的ClassLoader的reference(引用)。

批注:在Class类中有成员变量ClassLoader,所以Class类有getClassLoader()方法。

数组类的类对象不是由类加载器创建的,而是根据 Java 运行时的需求自动创建的。 Class.getClassLoader()返回的数组类的类加载器与其元素类型的类加载器相同; 如果元素类型是原始类型,则数组类没有类加载器。 样例说明 public class ClassLoaderTest3 { public static void main(String[] args) { String[] strings = new String[2]; System.out.println(strings.getClass().getClassLoader());//元素类型是String,由BootStrap加载 System.out.println("---------------------"); ClassLoaderTest3[] classLoaderTest3s = new ClassLoaderTest3[2]; System.out.println(classLoaderTest3s.getClass().getClassLoader()); System.out.println("---------------------"); int[] a = new int[2]; System.out.println(a.getClass().getClassLoader()); } } 结果 image.png 应用程序实现(继承)ClassLoader,用以扩展 Java 虚拟机动态加载类的方式。

批注:自定义类加载器的目的是扩展 Java 虚拟机动态加载类的方式,在默认情况是以双亲委托机制来加载类的,如果你不喜欢双亲委托机制,就自行继承ClassLoader,实现不一样的加载方式。

安全管理器通常可以使用类加载器来指示安全域。

批注:俺看不懂,自行百度。

ClassLoader类使用委托模型来搜索类和资源。 ClassLoader 的每个实例都有一个关联的父类加载器。 当请求查找类或资源时, ClassLoader实例会将类或资源的搜索委托给其父类加载器,然后再尝试查找类或资源本身。 虚拟机的内置类加载器,称为“引导类加载器(Bootstrap ClassLoader)”,它本身没有父级,但可以作为ClassLoader实例的父级。 支持并发加载类的类加载器被称为具有并行能力的类加载器,并且需要在类初始化时通过调用ClassLoader.registerAsParallelCapable方法来注册自己,使其可以并行加载类。 请注意, ClassLoader类默认注册为具有并行能力。 但是,虽然它们具有并行能力,但它的子类仍然需要注册自己。 在委托模型不是严格分层的环境中,类加载器需要具有并行能力,否则类加载会导致死锁,因为加载器锁在类加载过程中一直保持着(参见loadClass方法)。 通常,Java 虚拟机以平台相关的方式从本地文件系统加载类。 例如,在 UNIX 系统上,虚拟机从CLASSPATH环境变量定义的目录中加载类。 但是,有些类可能不是来自文件; 它们可能来自其他来源,例如网络,或者它们可以由应用程序构建。 方法 defineClass 将一个字节数组转换为Class 类的一个实例。 可以使用 Class.newInstance 创建这个新定义的类的实例。 类加载器创建的对象的方法和构造函数中有引用其他类。 为了确定所引用的类,Java 虚拟机调用最初创建类的类加载器的loadClass方法。

批注:说一个对象里面的方法有使用其它的类,如,method(){return new String("hehe");},引用了String类。

来一些代码来增加一下理解 自定义类加载器 在开始之前先提一下,上面的文档说了: defineClass 将一个字节数组转换为Class 类的一个实例。 可以使用 Class.newInstance 创建这个新定义的类的实例。 defineClass 参数 image.png 再说了,需要让类加载器找到需要加载的类,它才能加载,上面提到的binary name就很关键。 这个自定义代码有些问题,先看看 public class UserClassLoader extends ClassLoader { private String classLoaderName; private final String SUFFIX = ".class"; private String path; public void setPath(String path) { this.path = path; } public UserClassLoader(String classLoaderName) {//构造方法 super();//以系统类加载器为该加载器的父加载器 this.classLoaderName = classLoaderName; } //构造方法,可以指定父类加载器的构造方法 public UserClassLoader(ClassLoader parent, String classLoaderName) { super(parent);//显式定义该类加载器的父加载器 this.classLoaderName = classLoaderName; } @Override public String toString() { return "classLoaderName='" + classLoaderName; } @Override protected Class findClass(String className) {//重写 byte[] data = loaderClassData(className); return defineClass(className,data,0,data.length);//字节数组变成Class } private byte[] loaderClassData(String className) {//将class文件变成字节数组 InputStream in = null; ByteArrayOutputStream outputStream = null; byte[] data = null; try { className = className.replace(".", "/"); in = new FileInputStream(path + className + SUFFIX);//文件输入流 outputStream = new ByteArrayOutputStream();//字节输出流 int ch; while ((ch = in.read()) != -1) { outputStream.write(ch); } data = outputStream.toByteArray();//字节数组 } catch (Exception e) { e.printStackTrace(); } finally { Optional.ofNullable(in).ifPresent(i -> { try { i.close();//关闭流 } catch (IOException e) { e.printStackTrace(); } }); Optional.ofNullable(outputStream).ifPresent(i -> { try { i.close();//关闭流 } catch (IOException e) { e.printStackTrace(); } }); } return data;//返回字节数组。数组内容就是类 } public static void main(String[] args) throws Exception { UserClassLoader userClassLoader = new UserClassLoader("userClassLoader"); UserClassLoader.test(userClassLoader); } public static void test(ClassLoader classLoader) throws Exception {//测试 //指定要加载的class文件,不能是当前类,类名是binary name Class clazz = classLoader.loadClass("com.jvmstudy.classloading.ClassLoaderTest"); Object object = clazz.newInstance();//生成类的实例 System.out.println(object);//打印 } } 结果,毫无疑问就是类名

image.png

那么问题来了,上面自定义的类加载器有被使用吗 在上面的代码中,在findClass方法中添加一下打印语句 @Override protected Class findClass(String className) { System.out.println("findClass invoke: "+className); System.out.println("classLoader name: "+classLoaderName); byte[] data = loaderClassData(className); return defineClass(className,data,0,data.length); } 结果,加载类的时候并没有使用自定义类加载去加载。

image.png

再在test方法中打印一下类是由哪个加载器加载的 public static void test(ClassLoader classLoader) throws Exception { //加载类,不能是当前类 Class clazz = classLoader.loadClass("com.jvmstudy.classloading.ClassLoaderTest"); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); 结果,该类是由系统(App)类加载器加载的

image.png

为什么会出现自定义类加载器没有被使用的情况 可以看到在main方法里,实例化自定义类加载器时,用的是第一个构造方法。 public UserClassLoader(String classLoaderName) {//构造方法 super();//以系统类加载器为该加载器的父加载器 this.classLoaderName = classLoaderName; } 根据双亲委托机制, 当使用自定义类加载器加载类时,它会委托其父类加载器(AppClassLoader,即系统类加载器)去加载。所以,最终是系统类加载器加载的"com.jvmstudy.classloading.ClassLoaderTest" 更进一步 系统类加载器是加载当前类路径下的类,就说.java文件编译后的.class文件默认输出的路径

image.png

删除默认路径下加载的class文件。 image.png

image.png

main方法如下:

public static void main(String[] args) throws Exception { UserClassLoader userClassLoader = new UserClassLoader("userClassLoader"); userClassLoader.setPath("C:/Users/25852/Desktop/"); //不能是当前类 Class clazz = userClassLoader.loadClass("com.jvmstudy.classloading.ClassLoaderTest"); Object object = clazz.newInstance(); System.out.println("class HashCode: "+clazz.hashCode()); System.out.println(object.getClass().getClassLoader()); }

结果: image.png

原因何在?

根据双亲委托机制,还是自定义类加载器不会自己先加载类,但是,其父类加载器都无法加载类,根类加载器和扩展类加载器就不用说了肯定加载不了,系统类加载器也加载不了,因为默认路径中的class文件已经被删除了。 最终,都委托了一遍,就只能是自定义类自己加载了,我们已经告诉自定义类加载去桌面加载类了。 重新编译一下,将原本删除的class文件重新生成一下 main方法改编如下: public static void main(String[] args) throws Exception { UserClassLoader userClassLoader = new UserClassLoader("userClassLoader"); Class clazz = userClassLoader.loadClass("com.jvmstudy.classloading.ClassLoaderTest");//不能是当前类 Object object = clazz.newInstance(); System.out.println("class HashCode: "+clazz.hashCode()); System.out.println(object.getClass().getClassLoader()); System.out.println("-----------------------"); UserClassLoader userClassLoader2 = new UserClassLoader("userClassLoader2"); Class clazz1 = userClassLoader2.loadClass("com.jvmstudy.classloading.ClassLoaderTest");//不能是当前类 Object object1 = clazz1.newInstance(); System.out.println("class HashCode: "+clazz.hashCode()); System.out.println(object1.getClass().getClassLoader()); } 结果:

image.png

说明同一个类只会被加载一次。 老样子,删掉默认路径下的class,将其移到桌面 main方法改编如下: public static void main(String[] args) throws Exception { UserClassLoader userClassLoader = new UserClassLoader("userClassLoader"); userClassLoader.setPath("C:/Users/25852/Desktop/"); Class clazz = userClassLoader.loadClass("com.jvmstudy.classloading.ClassLoaderTest");//不能是当前类 Object object = clazz.newInstance(); System.out.println("class HashCode: "+clazz.hashCode()); System.out.println(object.getClass().getClassLoader()); System.out.println("-----------------------"); UserClassLoader userClassLoader1 = new UserClassLoader("userClassLoader1"); userClassLoader1.setPath("C:/Users/25852/Desktop/"); Class clazz1 = userClassLoader1.loadClass("com.jvmstudy.classloading.ClassLoaderTest");//不能是当前类 Object object1 = clazz1.newInstance(); System.out.println("class HashCode: "+clazz1.hashCode()); System.out.println(object1.getClass().getClassLoader()); } 结果:

image.png

前面才说同一个类只会被加载一次,这里怎么就不对了? 这就涉及到类加载器的命名空间问题了。 下亿篇博客再介绍命名空间问题。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3