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 loaded
Class<?> 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 stats
sun.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 {
@Override
public 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方法。这种方式实际上是先尝试子类加载器,然后才是父类加载器,与双亲委派模型的顺序相反。