class 装载验证流程

  • 加载

  • 链接

    • 验证

    • 准备

    • 解析

  • 初始化

class 装载验证流程 - 加载

  • 装载类的第一个阶段

  • 取得类的二进制流

  • 转为方法区数据结构

  • 在 Java 堆中生成对应的 java.lang.Class 对象

class 装载验证流程 - 链接 验证

  • 目的:保证 Class 流的格式是正确的

  • 文件格式的验证

    • 是否以 0xCAFEBABE 开头

    • 版本号是否合理

  • 元数据验证

    • 是否有父类

    • 继承了 final 类?

    • 非抽象类实现了所有的抽象方法

  • 字节码验证 (很复杂)

    • 运行检查

    • 栈数据类型和操作码数据参数吻合

    • 跳转指令指定到合理的位置

  • 符号引用验证

    • 常量池中描述类是否存在

    • 访问的方法或字段是否存在且有足够的权限

class 装载验证流程 - 链接 准备

  • 分配内存,并为类设置初始值 (方法区中)

    • public static int v=1;

    • 在准备阶段中,v 会被设置为 0

    • 在初始化的中才会被设置为 1

    • 对于 static final 类型,在准备阶段就会被赋上正确的值

    • public static final int v=1;

class 装载验证流程 - 链接 解析

  • 符号引用替换为直接引用

  • 字符串引用对象不一定被加载

  • 指针或者地址偏移量引用对象一定在内存

class 装载验证流程 – 初始化

  • 执行类构造器

    • static 变量 赋值语句

    • static{} 语句

  • 子类的调用前保证父类的被调用

  • 是线程安全的

Java.lang.NoSuchFieldError 错误可能在什么阶段抛出?

什么是类装载器 ClassLoader

  • ClassLoader 是一个抽象类

  • ClassLoader 的实例将读入 Java 字节码将类装载到 JVM 中

  • ClassLoader 可以定制,满足不同的字节码流获取方式

  • ClassLoader 负责类装载过程中的加载阶段

JDK 中 ClassLoader 默认设计模式

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)

    寻找已经加载的类

JDK 中 ClassLoader 默认设计模式 – 分类

  • BootStrap ClassLoader (启动 ClassLoader)

  • Extension ClassLoader (扩展 ClassLoader)

  • App ClassLoader (应用 ClassLoader / 系统 ClassLoader)

  • Custom ClassLoader(自定义 ClassLoader)

  • 每个 ClassLoader 都有一个 Parent 作为父亲, 启动 ClassLoader 除外

JDK 中 ClassLoader 默认设计模式 – 协同工作

QQ截图20170430232552.png

图片1.png

JDK 中 ClassLoader 默认设计模式

public class HelloLoader {
public void print(){
    System.out.println("I am in apploader");
    }
}

放到 D:/tmp/clz

public class HelloLoader {
public void print(){
    System.out.println("I am in bootloader");
    }
}
public class FindClassOrder {
public static void main(String args[]){
HelloLoader loader=new HelloLoader();
loader.print();
    }
}
  • 直接运行以上代码:

    I am in apploader

  • 加上参数 -Xbootclasspath/a:D:/tmp/clz

    I am in bootloader

  • 此时 AppLoader 中不会加载 HelloLoader

    I am in apploader 在 classpath 中却没有加载

    说明类加载是从上往下的

强制在 apploader 中加载

public static void main(String args[]) throws Exception {
    ClassLoader cl=FindClassOrder2.class.getClassLoader();
    byte[] bHelloLoader=loadClassBytes("geym.jvm.ch6.findorder.HelloLoader");
    Method md_defineClass=ClassLoader.class.getDeclaredMethod("defineClass", byte[].class,int.class,int.class);
    md_defineClass.setAccessible(true);
    md_defineClass.invoke(cl, bHelloLoader,0,bHelloLoader.length);
    md_defineClass.setAccessible(false);

    HelloLoader loader = new HelloLoader();
    System.out.println(loader.getClass().getClassLoader());
    loader.print();
}

-Xbootclasspath/a:D:/tmp/clz

在查找类的时候,先在底层的 Loader 查找,是从下往上的。Apploader 能找到,就不会去上层加载器加载

能否只用反射,仿照上面的写法,将类注入启动 ClassLoader 呢?

JDK 中 ClassLoader 默认设计模式 – 问题

双亲模式的问题:

顶层 ClassLoader,无法加载底层 ClassLoader 的类

Java 框架 (rt.jar) 如何加载应用的类?

javax.xml.parsers 包中定义了 xml 解析的类接口 Service Provider Interface SPI 位于 rt.jar 即接口在启动 ClassLoader 中。而 SPI 的实现类,在 AppLoader。

JDK 中 ClassLoader 默认设计模式 – 解决

  • 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

破坏双亲模式例子 - 先从底层 ClassLoader 加载

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // First, check if the class has already been loaded
    Class re=findClass(name);
    if(re==null){
        System.out.println(“无法载入类:+name+“ 需要请求父加载器");
        return super.loadClass(name,resolve);
    }
    return re;
}
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);
if (null == clazz) {
    try {
        String classFile = getClassFile(className);
        FileInputStream fis = new FileInputStream(classFile);
        FileChannel fileC = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel outC = Channels.newChannel(baos);
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
         省略部分代码
        fis.close();
        byte[] bytes = baos.toByteArray();

        clazz = defineClass(className, bytes, 0, bytes.length);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
return clazz;
}
OrderClassLoader myLoader=new OrderClassLoader("D:/tmp/clz/");
Class clz=myLoader.loadClass("geym.jvm.ch6.classloader.DemoA");
System.out.println(clz.getClassLoader());

System.out.println("==== Class Loader Tree ====");
ClassLoader cl=myLoader;
while(cl!=null){
    System.out.println(cl);
    cl=cl.getParent();
}
java.io.FileNotFoundException: D:\tmp\clz\java\lang\Object.class (系统找不到指定的路径。)
    at java.io.FileInputStream.open(Native Method)
    .....
    at geym.jvm.ch6.classloader.ClassLoaderTest.main(ClassLoaderTest.java:7)
无法载入类:java.lang.Object需要请求父加载器
geym.jvm.ch6.classloader.OrderClassLoader@18f5824
==== Class Loader Tree ====
geym.jvm.ch6.classloader.OrderClassLoader@18f5824
sun.misc.Launcher$AppClassLoader@f4f44a
sun.misc.Launcher$ExtClassLoader@1d256fa

DemoA 在 ClassPath 中,但由 OrderClassLoader 加载

因为先从 OrderClassLoader 加载,找不到 Object,之后使用 appLoader 加载 Object

如果 OrderClassLoader 不重载 loadClass(),只重载 findClass,那么程序输出为

sun.misc.Launcher$AppClassLoader@b23210
==== Class Loader Tree ====
geym.jvm.ch6.classloader.OrderClassLoader@290fbc
sun.misc.Launcher$AppClassLoader@b23210
sun.misc.Launcher$ExtClassLoader@f4f44a

热替换

  • 当一个 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)

资料