一、类加载过程
1. 装载(Load)
- 通过一个类的全限定名 获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在Java堆中生成一个代表这个类的 java.lang.Class对象,作为对该方法区中这些数据的访问入口
2.链接(Link)
- 验证(Verify)
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
- 准备(Prepare)
为静态类分配内存,并将其初始化为默认值 - 解析(Resolve)
把类中符号引用转换为直接引用
3. 初始化
对类的静态变量,静态代码块执行初始化操作
4.类加载过程图解
二、 类加载器
实现类的加载动作,将类文件转换为二进制流的供链接阶段使用,装载Class文件;
2.1 类与类加载器
对于任意一个类,都需要由他的类加载器和这个类本身一同确定其在java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间;
列:比较两个类是否"相等",只用在这两个类是由同一个类加载器加载的前提系下才有意义,否则,即使这两个类来源与同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那么这两个类也相等;
这里的相等代表类的Class对象的equals()、isAssignableFrom()、isInstance()、instanceOf关键字做对象所属关系判定等情况
2.2 加载器分类
- Bootstrap ClassLoader:负责加载$JAVA_HOME中,jre/lib/rt.jar里所有的class或Xbootclasspath选项指定的jar包,由C++实现,不是ClassLoader子类
- Extension ClassLoader:负责加载java平台中扩展共能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
- Application ClassLoader:负责加载classpath中指定的jar包及 Djava.class.path所指定目录下的类和jar包
- Custom ClassLoader:通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义ClassLoader,如Tomcat、jboss都会根据J2EE ,自行实现ClassLoader
2.3 双亲委派机制
如图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型;双亲委派模型要求除了顶层额启动类加载器之外,其余的类加载器都应当有自己的父加载器;类加载器之间的父子关系一般不会以继承的关系来实现,而都是使用的组合关系来复用父加载器。
双亲委派机制,不是一个强制性的约束请求,而是java设计者推荐给开发者的一种类加载方式
-
检查顺序
自底向上,从 Custom ClassLoader到Bootstrap ClassLoader逐层检查,只要某个ClassLoader已加载,就视为已加载此类,保证此类之被加载一次 -
加载顺序
加载的顺序是自上向下的,也就是由上层来逐渐尝试加载此类;
2.3.1 加载过程
如果一个类加载器收到了类加载的请求,他不会自己去尝试加载这个类,而是把请求委派给自己的父类加载器去完成,依次递归;只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。
2.3.2 优势
Java类随着加载他的类加载器一起具备一个一种带有优先级的层次关系,比如,Object类,它存放在rt.jar中,无论哪一个类加载器去加载它,最终都会委派给处于模型最顶端的启动类加载器,因此Object类在程序个各种加载器环境中都是同一个类;如果没有双亲委派机制的,由各个类自己去加载的话,那么系统中会存在多种不同的Object类。
2.3.3 双亲委派机制代码实现
//代码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 检查请求的类是否已被加载过
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) {
//如果父类加载器抛出异常,说明加载器无法完成加载请求
}
if (c == null) {
//父类无法加载,调用本身的findClass()
long t1 = System.nanoTime();
c = findClass(name);
// 记录类加载器信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
2.3.5 破坏双亲委派机制
- JDK 1.2之前,用户自定义的类加载器会重写loadClass(),挽救措施—不提倡用户再去覆盖loadClass()方法,将类加载逻辑写道findClass()方法中,在loadClass()方法逻辑中如果父类加载失败,则会调用自己的findClass()方法来完成加载
- 模型自身缺陷,推出了上下文类加载器,去加载父类所需要的SPI代码,也就是父类加载器请求子类的加载器去完成类加载动作;例如:JNDI、JDBC、JCE、JAXB和 JBI
- 热部署代码热替换(HotSwap)、模块热部署(Hot Deployment),在不重启的情况下,实现替换自动加载更新