类装载器

本博客为炼数成金JVM教程的第六课

目录

  1. class装载验证流程
  2. 什么是类装载器ClassLoader
  3. JDK中ClassLoader默认设计模式
  4. 打破常规模式
  5. 热替换

class 装载验证流程

  • 加载
  • 链接
    • 验证
    • 准备
    • 解析
  • 初始化

加载
装载类的第一个阶段
取得类的二进制流
转为方法区数据结构
在Java堆中生成对应的java.lang.Class 对象

链接 -> 验证
目的: 保证Class流的格式是正确的

1 文件格式的验证

  • 是否以 0xCAFEBABE 开头
  • 版本号是否合理(JDK6 可能 无法运行JDK7生成的class文件)

2 元数据验证

  • 是否有父类
  • 继承了final类
  • 非抽象类实现了所有的抽象方法

3 字节码验证(很复杂)

  • 运行检查
  • 栈数据类型和操作码数据参数吻合
  • 跳转指令指定到合理的位置

4 符号引用验证

  • 常量池中描述类是否存在
  • 访问的方法或字段是否存在且有足够的权限

链接 -> 准备

  • 分配内存,并为类设置初始值(方法区中)
    public static int v = 1;
    在准备阶段,v会被设置为0,在初始化的clinit 中才会被设置为1
    public static final int v = 1;
    在准备阶段,v会被设置为1
    对于static final 类型,在准备阶段就会被赋上正确的值

链接-> 解析

  • 符号引用替换为直接引用
    • 符号引用 -> 字符串:应用对象不一定被加载
    • 直接引用 -> 指针或者地址偏移量:引用对象一定在内存中

初始化

  • 执行类构造器clinit(class init)
    • static 变量 赋值语句执行
    • static{} 语句 执行
  • 子类的clinit调用前保证父类的clinit被调用(如果子类的先被调用,对于子类来说,父类的信息就取不到)
  • clinit是线程安全的(一个线程进去后,其余的线程会等待)

什么是类装载器ClassLoader

  • ClassLoader 是一个抽象类
  • ClassLoader的实例将读入Java字节码,将类装载到JVM中
  • ClassLoader可以定制,满足不同的字节码流获取方式(可以从网络,文件等等地方加载class文件)
  • 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)
    • 寻找已经加载的类

ClassLoader默认设计模式 - 分类

BootStrap ClassLoader (启动ClassLoader)
Extension ClassLoader (扩展ClassLoader)
App ClassLoader (应用ClassLoader/系统ClassLoader)
Custom ClassLoader(自定义ClassLoader)
除了启动ClassLoader,每个ClassLoader都有一个Parent作为父亲

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

协同工作

首先自底向上检查类是否已经加载:
我们自己写的类都在App ClassLoader中。当我们去找一个类的时候,我们先在当前的Classloader里面找。
比如我们去找一个自己写的类,那么先在App ClassLoader中找,是否已经加载了这个类,如果没有找到,不会做加载,而是把这个请求请求到Extension ClassLoader,如果Extension ClassLoader 有,则返回该类,如果没有,那么就去找 Bootstrap ClassLoader中有没有这个类。如果有,那么返回,如果没有,那么在这个系统中都没有这个类,就会去尝试做加载。
加载的过程如下:
自顶向下的类加载方式
我们先在bootstrap ClassLoader中加载该类,如果加载成功,则返回class,如果bootstrap ClassLoader加载不成功,那么就让Extension ClassLoader 去加载,依次类推。

loadClass 原码实现
loadClass 原码

尝试加载一个类的时候,先去找这个类是否已经被加载,如果已经被加载,就返回这个类。意味着一个类不会反复被加载。如果找不到,那么就会请求父类的loader去加载。

我们看下面的例子:
这两个类在同一个文件夹下 C:\Users\Administrator\Desktop\code

public class HelloLoader {
    public void print(){
        System.out.println("I am in apploader");
        System.out.println("===");
    }
}
public class FindClassOrder {
    public static void main(String args[]){
    HelloLoader loader=new HelloLoader();
    loader.print();
    }
}

输出结果如下:

这里写图片描述

此时,我们再写一个相同的HelloLoader类
把这个类的class文件随意放一个地方C:\Users\Administrator\Desktop\code2

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

使用如下方法调用且结果如下:
这里写图片描述

此时,App ClassLoader中不会加载HelloLoader,因为在检查HelloLoader时,会从底层往上找,找不到,就会从顶层往下加载类,在bootstrap ClassLoader中已经找到了HelloLoader,所以不会使用到app ClassLoader中的HelloLoader。
可见,类的加载是从上往下的。

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

这里写图片描述
双亲模式的问题: 顶层ClassLoader,无法加载底层ClassLoader的类
也就是在bootstrap ClassLoader无法加载App ClassLoader中的类,没有办法生成该类的对象,该类的实例。

JDK中ClassLoader默认设计模式 - 问题解决

Thread.setContextClassLoader()
上下文加载器
是一个角色(假想为班长的身份,只是一个角色,谁都可以做。例如我们可以把App ClassLoader设置为上下文加载器)
用于解决顶层ClassLoader无法访问底层ClassLoader的类的问题
基本思想是: 在顶层ClassLoader中,传入底层ClassLoader的实例

代码来自于javax.xml.parsers.FactoryFinder
展示如何在启动类加载器加载AppLoader的类

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());
        }

代码显示了如何在FactoryFinder这个类中,加载App ClassLoader 中的类。代码中cl 就是上下文的类加载器。在拿到了上下文加载器之后,就可以加载在App ClassLoader中的类。
上下文ClassLoader可以突破双亲模式的局限性。

双亲模式的破坏
双亲模式是默认的,但不是必须这么做
Tomcat的WebappClassloader 就会先加载自己的Class(违背了双亲模式),找不到再委托parent
OSGI的Classloader形成网状结构,根据需要自由家在Class

破坏双亲模式例子:先从底层ClassLoader加载
OrderClassLoader的部分实现

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;
}

先会尝试自己去加载类,如果找不到,那么就去父类ClassLoader中查找。
我们看看findClass() 方法的实现

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;
}

首先去查找类(findLoaderdClass)是否被加载,如果已经加载了则直接返回。
如果找不到,自己读文件,去把二进制数据读上来。调用defineClass() 去把类真正的定义起来。
可见,findClass() 方法会去查找类定义类加载类,因此就达到了刚才说的从底层加载类的目的。

热替换
当一个class被替换之后,系统无需重启,替换的类立即生效。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值