Java类加载机制是Java虚拟机(JVM)的一个核心组成部分,它负责将Java类从不同的数据源(如本地文件系统、网络等)加载到JVM中,并为之生成对应的java.lang.Class对象。理解Java类加载机制对于深入理解Java运行时的行为、解决类加载相关的问题以及进行高级框架开发都具有重要意义。
类加载的生命周期
// 加载
硬盘查找并通过IO读入字节码文件(Java编译过的.class文件),这里是懒加载,只有使用时才会加载(通过类加载器),加载会在内存中(JVM的元空间)生成一个代表这个类的Class对象.
// 验证
校验字节码文件的正确性.比如格式、jdk版本号等.
// 准备
为类的静态变量分配内存,并将其初始化为默认值(例如ingt默认值0)
// 解析
将类中的符号引用转换为直接引用。
这里的'符号引用'指的是类文件常量池中的各种引用,它们以一种抽象的方式引用其他类、接口、字段和方法,而这些引用在类文件中并不直接指向目标的内存地址。当类被加载解析时需要将这些'符号引用'转化为'直接引用'.
// 初始化
初始化阶段是执行类构造器<clinit>()方法的过程。这个方法由编译器自动合成,包含了类变量的赋值操作和静态代码块中的语句。
// 使用
在完成了加载、链接(验证、准备、解析)和初始化之后,类就可以被程序使用了,比如创建类的实例、访问类的静态变量和调用类的静态方法等。
// 卸载
类在长时间没有被使用且没有任何引用时,JVM会考虑将其卸载,释放掉占用的资源.
类加载器
Java虚拟机通过类加载器来实现类的加载。Java中的类加载器通常分为以下几种:
// 启动类加载器(Bootstrap ClassLoader)
它是虚拟机自带的类加载器,负责加载JAVA_HOME/lib目录中的类库。
// 扩展类加载器(Extension ClassLoader)
它负责加载JAVA_HOME/lib/ext目录中的类库。
// 应用程序类加载器(Application ClassLoader)
它负责加载用户类路径(Classpath)上所指定的类库。
// 自定义类加载器
用户还可以通过继承java.lang.ClassLoader类的方式来创建自定义的类加载器。
双亲委派机制
双亲委派机制(Parent Delegation Model)是Java类加载器(ClassLoader)采用的一种类加载机制。在这种机制下,类加载器在尝试加载一个类时,会先委托给其父加载器进行加载,每一级加载器都是如此,直到顶层的启动类加载器(Bootstrap ClassLoader)。只有当父加载器无法完成这个加载请求(它在自己的搜索范围内没有找到所需的类)时,子加载器才会尝试自己去加载这个类。
// 加载user.class的过程
1.首先AppClassLoader会在自己的缓存中进行判断,user.class是否已经被加载过,有就直接返回,若没有则会委托它的父类加载器ExtClassLoader.
2.ExtClassLoader也是同样的先在自己的缓存里判断是否加载过user.class,
有就直接返回,若没有则会委托它的父类加载器BootStrapClassLoader.
3.BootStrapClassLoader也是判断缓存中是否有,没有就加载,没有加载到就交给
ExtClassLoader来加载,如果还没有就轮到AppClassLoader加载.
4.最终由AppClassLoader加载user.class.
双亲委派机制的优点
1.避免类的重复加载:由于在顶层的启动类加载器开始往下查找,因此类只会被加载一次,避免了在JVM中类的多份副本。
2.保护程序安全:通过这种机制可以确保Java核心库的类型安全,防止核心API被随意篡改。
自定义类加载器
自定义类加载器需要继承 java.lang.ClassLoader 类,这个类有两个核心方法
// loadClass() 实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {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 statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}// findClass()默认实现是空方法
// 所以我们自定义类加载器主要是重写findClass方法。protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}
注:自定义类加载器默认父加载器是AppClassLoader。
打破双亲委派机制
打破双亲委派机制通常是指在自定义类加载器时,改变类加载器默认的行为,即不完全遵循双亲委派模型。在某些特定场景下,这样做是有必要的,例如Tomcat部署多个应用程序,每个程序可能依赖第三方框架的不同版本.
例子
要打破双亲委派机制,可以通过覆盖ClassLoader类的loadClass(String name, boolean resolve)方法来实现。
public class CustomClassLoader extends ClassLoader {@Overridepublic Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {// 首先,检查请求的类是否已经被加载Class<?> clazz = findLoadedClass(name);if (clazz == null) {try {// 尝试用自定义的方式加载类clazz = findClass(name);} catch (ClassNotFoundException e) {// 如果自定义方式失败,调用父类加载器尝试加载clazz = super.loadClass(name, resolve);}}if (resolve) {resolveClass(clazz);}return clazz;}
}
在这个例子中,CustomClassLoader首先尝试通过自定义的方式(findClass方法)来加载类,只有在失败时才回退到调用父类加载器的loadClass方法。这种方式实际上是先尝试子类加载器,然后才是父类加载器,与双亲委派模型的顺序相反。