问:ClassLoader 的 defineClass、loadClass、findClass 方法分别有什么区别?
答:ClassLoader 用来加载 Java 类到 Java 虚拟机中(也就是将 class 文件加载成运行时的 Class 实例),所以问题中的几个方法其实就是负责这个加载过程中不同职责的定义。其中
loadClass(String name)
方法加载名称为 name 的类,返回的结果是java.lang.Class
类实例;findClass(String name)
方法用来查找名称为 name 的已经被该 ClassLoader 加载过的类,返回的结果是java.lang.Class
类实例;defineClass(String name, byte[] b, ...)
方法用来把字节数组 b 中的内容转换成 class 类,返回的结果是java.lang.Class
类实例。
问:简单说说你对 ClassLoader 的理解?
答:ClassLoader 的作用是根据一个指定的类名称找到或者生成其对应的字节代码,然后把字节码转换成一个 Java 类(即 java.lang.Class
实例),除此之外还负责加载 Java 应用所需的资源、Native lib 库等。
Java 的类加载器大致可以分成系统类加载器和应用开发自定义类加载器。系统类加载器主要有如下几个:
引导类加载器(bootstrap class loader):用来加载 Java 核心库,是虚拟机中用原生代码实现的,没有继承自 ClassLoader。
扩展类加载器(extensions class loader):用来加载 Java 的扩展库,虚拟机的实现会提供一个默认的扩展库目录,该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):用来加载应用类路径(CLASSPATH)下的 class,一般来说 Java 应用的类都是由它来完成加载的,可以通过
ClassLoader.getSystemClassLoader()
来获取它。
除了引导类加载器之外,所有的其他类加载器都有一个父类加载器(可以通过 ClassLoader
的 getParent()
方法得到)。系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器,开发自定义的类加载器的父类加载器是加载此类加载器的 Java 类的类加载器。所以类加载器在尝试自己去加载某个类时会先通过 getParent() 代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推,从而形成了双亲委派模式。类加载机制是通过 loadClass 方法触发的,查找类有没有被加载和该代理给哪个层级的加载器加载是由 findClass
方法实现的,而真正完成类加载工作是 defineClass
方法实现的。
问:Java 虚拟机是如何判断两个 Class 类是相同的?
答:Java 虚拟机不仅要看类的全名是否相同(含包名路径),还要看加载此类的类加载器是否一样,只有两者都相同的情况下才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类也是不同的,譬如一个 Java 类 cn.yan.Test 在编译后生成了字节码文件 Test.class,两个不同的类加载器 ClassLoaderA 和 ClassLoaderB 分别读取了这个 Test.class 文件,然后各自定义出一个 java.lang.Class
类的实例来表示这个类,这两个实例是不相同的,因为对于 Java 虚拟机来说它们是不同的类,这时候如果试图对这两个类的对象进行相互赋值则会抛出 ClassCastException
运行时异常。这在做插件化动态加载中要尤其注意。
本文参考自 ClassLoader 必知重点题目解析