一、class装载验证流程
加载
链接
验证
准备
解析
初始化
1.1 加载
装载类的第一个阶段,主要做以下3件事
- 取得类的二进制流
- 转为方法区数据结构
- 在Java堆中生成对应的java.lang.Class对象
1.2 链接 -> 验证
目的:保证Class流的格式是正确的
- 文件格式的验证
- 是否以0xCAFEBABE开头
- 版本号是否合理
- 元数据验证
- 是否有父类
- 继承了final类?
- 非抽象类实现了所有的抽象方法
- 字节码验证 (很复杂)
- 运行检查
- 栈数据类型和操作码数据参数吻合
- 跳转指令指定到合理的位置
- 符号引用验证
- 常量池中描述类是否存在
- 访问的方法或字段是否存在且有足够的权限
1.3 链接 -> 准备
分配内存,并为类设置初始值 (方法区中)
public static int v=1;
在准备阶段中,v会被设置为0
在初始化的<clinit>中才会被设置为1
对于static final类型,在准备阶段就会被赋上正确的值
public static final int v=1;
1.4 链接 -> 解析
1.5 初始化
执行类构造器<clinit> static变量 赋值语句 static{}语句 子类的<clinit>调用前保证父类的<clinit>被调用 <clinit>是线程安全的
1.6 问题
Java.lang.NoSuchFieldError错误可能在什么阶段抛出?
二、什么是类装载器ClassLoader
ClassLoader是一个抽象类 ClassLoader的实例将读入Java字节码将类装载到JVM中 ClassLoader可以定制,满足不同的字节码流获取方式 ClassLoader负责类装载过程中的加载阶段
三、JDK中ClassLoader默认设计模式
3.1 默认设计模式
ClassLoader的重要方法 public Class<?> loadClass(String name) throws ClassNotFoundException 载入并返回一个Class protected final Class<?> defineClass(byte[] b, int off, int len) 定义一个类,不公开调用 protected Class<?> findClass(String name) throws ClassNotFoundException loadClass回调该方法,自定义ClassLoader的推荐做法 protected final Class<?> findLoadedClass(String name) 寻找已经加载的类
3.2 分类
△ BootStrap ClassLoader (启动ClassLoader) △ Extension ClassLoader (扩展ClassLoader) △ App ClassLoader (应用ClassLoader/系统ClassLoader) △ Custom ClassLoader(自定义ClassLoader) 每个ClassLoader都有一个Parent作为父亲
3.3 协同工作模式
3.4 示例1
直接运行以上代码: I am in apploader 加上参数 -Xbootclasspath/a:D:/tmp/clz I am in bootloader 此时AppLoader中不会加载HelloLoader I am in apploader 在classpath中却没有加载 说明类加载是从上往下的
3.5 示例2
3.6 问题1
能否只用反射,仿照上面的写法,将类注入启动ClassLoader呢?
3.7 问题2
Thread. setContextClassLoader() 上下文加载器 是一个角色 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例 解决 static private Class getProviderClass(String className, ClassLoader cl, boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException { try { if (cl == null) { if (useBSClsLoader) { return Class.forName(className, true, FactoryFinder.class.getClassLoader()); } else { cl = ss.getContextClassLoader(); if (cl == null) { throw new ClassNotFoundException(); } else { return cl.loadClass(className); //使用上下文ClassLoader } } } else { return cl.loadClass(className); } } catch (ClassNotFoundException e1) { if (doFallback) { // Use current class loader - should always be bootstrap CL return Class.forName(className, true, FactoryFinder.class.getClassLoader()); } ….. 代码来自于 javax.xml.parsers.FactoryFinder 展示如何在启动类加载器加载AppLoader的类 上下文ClassLoader可以突破双亲模式的局限性 双亲模式的破坏 双亲模式是默认的模式,但不是必须这么做 Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent OSGi的ClassLoader形成网状结构,根据需要自由加载Class
3.8 破坏双亲模式例子
先从底层ClassLoader加载
四、热替换
当一个class被替换后,系统无需重启,替换的类立即生效 例子: geym.jvm.ch6.hot.CVersionA public class CVersionA { public void sayHello() { System.out.println("hello world! (version A)"); } } DoopRun 不停调用CVersionA . sayHello()方法,因此有输出: hello world! (version A) 在DoopRun 的运行过程中,替换CVersionA 为: public class CVersionA { public void sayHello() { System.out.println("hello world! (version B)"); } } 替换后, DoopRun 的输出变为 hello world! (version B)