java中的类加载机制,ClassLoader是如何查找类的?
类似问法:描述一下 JVM 加载 class 文件的原理机制?
参考解答
1. 类加载的概念
类加载器(ClassLoader)是一组特殊的类,负责在运行时查找和加载其他类。
java中的类加载器分四个层次:
英文 | 中文 | 搜索路径 |
---|---|---|
Bootstrap ClassLoader | 系统类加载器 | JAVA_HOME/lib/rt.jar |
Extension ClassLoader | 扩展类加载器 | JAVA_HOME/lib/ext/*.jar |
App ClassLoader | 应用程序类加载器 | ClassPath |
Custom ClassLoader | 自定义类加载器 | 自定义 |
为了称呼方便把它们分别称为 BC, EC, AC, CC
如果定义了一个自己的类加载器:
ClassLoader cc = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 具体的加载类操作
}
};
cc.loadClass("java.lang.String"); // 情况1
cc.loadClass("entity.User"); // 情况2,User类在ClassPath上
cc.loadClass("enitty.Order"); // 情况3,Order类不在ClassPath上
工作流程为:
情况1: 希望通过cc加载java.lang.String类,这时cc会询问它的父亲ac看看这个类是否已经被加载,ac会询问它的父亲ec看看这个类是否已经被加载,最后ec去询问bc看这个类是否被加载。
java.lang.String类位于rt.jar包中,它会被bc所加载,因此再向下通知ec,ac,cc该类已经加载过了,这时cc中提供的findClass方法不会被调用。
情况2: 希望通过cc加载entity.User类,这时cc会询问它的父亲ac看看这个类是否已经被加载,ac会询问它的父亲ec看看这个类是否已经被加载,最后ec去询问bc看这个类是否被加载。
bc发现这个类没有被加载,但它只负责rt.jar中的类加载,因此将通知ec来加载这个类,ec只负责加载lib/ext/*.jar,因此又把加载委托给ac来完成,最后ac在ClassPath上找到了entity.User类,加载成功。 同样cc提供的findClass方法没必要调用了。
情况3: 希望通过cc加载entity.Order类,这时cc会询问它的父亲ac看看这个类是否已经被加载,ac会询问它的父亲ec看看这个类是否已经被加载,最后ec去询问bc看这个类是否被加载。
bc发现这个类没有被加载,但它只负责rt.jar中的类加载,因此将通知ec来加载这个类,ec只负责加载lib/ext/*.jar,因此又把加载委托给ac来完成,ac只负责ClassPath路径上的类加载,因此将加载委托给cc。 最后由cc调用findClass方法完成类加载。
2. 类加载的阶段
类加载分为3个阶段:硬加载、链接、初始化
硬加载(physical loading) 就是将*.class的字节码读取至内存的过程,但这个阶段,被加载类有哪些方法,引用了哪些其它类,有哪些属性,这些都还是未知的。
链接(linking) 这个阶段要完成字节码的校验、建立被加载类的基本内存结构(例如给静态变量分配空间)、以及解析操作(将符号引用转换为直接引用)。
初始化(initializing) 这个阶段执行静态变量的赋值,静态代码块中的代码。
3. 类加载中的重要方法
类加载是通过 ClassLoader类加载器来完成的,其中重要方法有:
- loadClass(String name) 是根据类名称加载类,完成加载阶段的操作,只会涉及前两个阶段,不会执行初始化阶段
- findClass(String name) 是提供给自定义类加载器的一种扩展方式,自定义类加载器会优先使用父类类加载器和系统类加载器来加载类,如果它们俩无法找到类时,最后会调用自定义类加载器的findClass方法来加载类
- defineClass(String name,byte[] bin,int offset, int limit) 是当类的字节码来源不是classpath上的文件时(例如,通过其它如网络、字节码生成技术生成的代表了类字节码的byte[]),可以调用此方法来完成加载类的工作。但它不应当被直接调用,一般是通过findClass间接调用