类加载机制
加载是指查找字节流,并且据此创建类的过程。加载需要借助类加载器,在 Java 虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。
ClassLoader双亲委派机制
类加载器加载类步骤:
- 首先会自底向上检测是否加载过此Class(即在缓存区中是否有此Class),如果有直接返回Class对象;
- 判断父加载器是否存在,如果存在父加载器,则请求父类加载器来载入此Class,如果成功则返回Class对象;如果不成功则使用子类加载器来载入此Class,如果成功则返回Class对象,如果不成功则抛出ClassNotFoundException()异常;
- 如果不存在父类加载器(要么父类加载器是根加载器;要么本身就是根加载器),请求根类加载器是来载入此Class,如果成功则返回Class对象,如果不成功则抛出ClassNotFoundException()异常;
源码浅析(关键方法)
loadClass(String name, boolean resolve)
name是类的名字,resolve是解析
404行会进入一个同步锁,因为有可能涉及到多个线程调用一个ClassLoader加载同一个类,保证线程安全,避免冲突;
406行Class<?> c = findLoadedClass(name);
会寻找曾经是否加载过目标Class,如果有会直接跳过407的if
和432的if
,直接return c;
,即返回目标Class对象;
如果没有加载过目标Class,会直接进入407行的 if
,408行会进入计时(可以忽略);则会判断parent
是否为空,如果parent
不为null
,那么当前ClassLoader的父加载器,再次进入loadClass
方法即401行;
如果当父加载器是BootStrapClassLoader
时或者自己本身就是BootStrapClassLoader
,由于BootStrapClassLoader
是C++编写的,parent
则会返回null
;
进入412行的else
, c = findBootstrapClassOrNull(name);
去查找BootStrapClassLoader
有没有载入过目标Class;若载入过,返回Class对象;否则会进入BootStrapClassLoader
目录下扫描是有目标Class,如果有返回,没有就会返回;
进入420行的if
, c = findClass(name);
会在当前ClassLoader目录中去查找是否有目标Class;如果有就装载目标Class并返回,如果没有就会轮到子类ClassLoader来查找,最终会到自定义ClassLoader去查找是否有目标Class,如果有返回,没有就会抛出ClassNotFoundException()异常;
ClassLoader之间的关系
package ClassLoader;
public class ClassLoaderDemo {
public static void main(String[] args) {
Class c = ClassLoaderDemo.class;
//由于是使用系统类加载器(非自定义加载器)来加载ClassLoaderDemo类,应该输出AppClassLoader
System.out.println(c.getClassLoader());
//AppClassLoader的父加载器应该是ExtClassLoader
System.out.println(c.getClassLoader().getParent());
//ExtClassLoader的父加载器应该是BootStrapClassLoader(BootStrapClassLoader应该输出null)
System.out.println(c.getClassLoader().getParent().getParent());
}
}
自定义ClassLoader——>AppClassLoader——>ExtClassLoader——>BootStrapClassLoader(null)
Tips:为什么BootStrapClassLoader是null?
在上面的413行代码中提到c = findBootstrapClassOrNull(name);
native
会去调用本地库或者外置都非Java代码(在openjdk中可以查看)
为什么要使用双亲委派机制加载类?
避免多份同样字节码重复加载,内存是有限的,没有必要保存相同的Class对象。
例如:System.out.println();
类A打印时会加载一次System类;类B打印时又会加载一次System类,那么内存中就会存在两份相同的System字节码,造成没必要的内存消耗;
如果使用双亲委派机制加载类,加载过后就不会再重复加载相同的Class;