类加载

类加载到jvm中的过程

  • java.exe调用dll文件创建java虚拟机
  • 创建引导类加载器,sun.mis.Launcher创建其他类加载器
  • 获取运行类自己的加载器,加载class文件(从具体位置读取)
  • 调用main方法
  • jvm销毁

初始化launcher

public Launcher() {
	//初始化两个类加载器,ExtClassLoader为单例加载
    ...
    var1 = Launcher.ExtClassLoader.getExtClassLoader();
    ...
    this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    ...
}

加载class文件 loadClass

  • 加载:调用类方法或者new时,会生成一个class文件
  • 验证:校验字节码文件的正确性
  • 准备:给类的静态变量分配内存,并赋予默认值
  • 解析:将符号引用替换为直接引用
  • 初始化:对类的静态变量初始化为指定的值,执行静态代码块

静态方法为何不能调用非静态的数据

  • 普通方法初始化的顺序在静态方法之后,类加载时静态方法就已经分配耗内存了,非静态方法则要等到类加载之后
// loadClass方法
protected Class<?> loadClass(String name, boolean resolve){
    // 检查当前类加载器是否已经加载了该类
 	Class<?> c = findLoadedClass(name);
 	...
 	// 判断paraent属性,由下自上找加载器,找到顶级后由上自下,双亲委派
 	 if (parent != null) {
     	c = parent.loadClass(name, false);
     } else {
         //引导类
     	c = findBootstrapClassOrNull(name);
     }
    ...
}

类加载器

  • bootstrapClassLoader:加载核心类库,jre lib下

  • ExtClassLoader:加载ext下jar包

  • AppClassLoader:加载classpath下,也就是我们自己写的class

  • 自定义

继承关系

  • 这几个类之间并没有继承关系,只是类中有一个parent属性指向上一级
  • ExtClassLoader的上一级虽然是bootstrapClassLoader,但是它由c++调用,所以赋值的时候给了null
  • 他们都继承URLClassLoader->ClassLoader

双亲委派

  • 加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类

为什么要这么设计

  • 可能是由于我们在使用时,大多数调用的类都是由我们自己编写的class,他们都是appClassLoader加载,第一次加载该类时虽然需要由下往上和由上往下找一次之后在加载到内存,但是后面我们再使用这个类时就不需要再次找寻

优点

  • 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改

  • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

自定义类加载器

  • 继承classLoader抽象类重写findClass方法
  • 两个模块依赖同一个jar包的不同版本
  • 从其他源获取加载数据
class MyClassLoader  extends ClassLoader{

    private String classPath;
    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private byte[] loadByte(String name) throws Exception {
        name = name.replaceAll("\\.", "/");
         FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
         int len = fis.available();
         byte[] data = new byte[len];
         fis.read(data);
         fis.close();
         return data;
    }
}

打破双亲委派机制

  • 重写loadClass方法,判断加载路径是否是自己需要的,按需加载
if (!name.startsWith("xxx")){
 c = this.getParent().loadClass(name);
}else{
 c = findClass(name);
}
posted @ 2023-06-26 10:08  france水  阅读(6)  评论(0编辑  收藏  举报