重拾Java之路-类加载

本文详细介绍了Java类加载机制,包括加载、验证、准备、解析和初始化五个阶段。讲解了双亲委派模型的优势,如避免类的重复加载和保证核心API的安全性。此外,还探讨了类加载器的分类,如启动类加载器、扩展类加载器和应用程序类加载器。最后,通过一个自定义类加载器的示例展示了如何打破双亲委派模型,并讨论了自定义类加载器的应用场景,如Tomcat的类隔离和加密代码加载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类加载机制

定义

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。

类加载过程

类加载的过程
类加载过程主要分为三个部分:

  1. 加载
  2. 链接
  3. 初始化

其中链接可以细分为 验证、准备和解析 三个部分。

需要注意的是,这几个阶段是按顺序开始,但不一定是顺序进行或完成的,一般是相互交叉混合进行的(解析阶段在某些情况下于初始化之后开始,为了支持Java语言的运行时绑定)

引申

绑定:把一个方法的调用与方法所在的类关联起来,分为静态绑定与动态绑定:

静态绑定:程序执行前已经被绑定。java当中的方法只有final,static,private和构造方法是前期绑定的。动态绑定也叫运行时绑定,几乎所有方法都是后期绑定的。

加载

定义:将类的 .class文件 中的二进制数据 读入到内存 中,将其放在运行时数据区的方法区内,然后在堆中创建一个Class对象,用于封装类在方法区的数据结构,并向程序员提供访问方法区内的数据结构接口。

验证

目的:确保.class文件中的字节流包含的信息符合虚拟机要求,一般包括四个阶段的验证:

  1. 文件格式验证:保证字节流能正确解析并存储于方法区内。后面的都是基于方法区的存储结构进行。
  2. 元数据验证:对类中各种数据类型进行语法校验。
  3. 字节码验证:进行数据流与控制流分析,对类方法体进行校验分析;
  4. 符号引用验证:对类自身以外的信息进行校验(访问性校验)。

准备

正式为静态变量分配内存并设置默认值。(针对的是类变量,并不包括实例变量,注意默认值与初始值区别)

解析

目的:将运行时常量池中的符号引用替换为直接引用。

符号引用:即一个字符串,给出了唯一识别一个方法,一个类,一个变量的信息。引用的目标不一定在内存中;

直接引用:一个内存地址(类方法,类变量)或者是一个偏移量(实例方法,实例变量)。如果存在直接引用,则引用的目标一定存在于内存中。

初始化

开始执行类中定义的Java代码,为类的静态变量赋予正确的初始值。

因此,可以得到:

  1. 类初始化方法一般在类初始化时执行;
  2. 对象初始化方法一般在实例化类对象的时候执行。

类加载器

分类

启动类加载器

由C++实现,是虚拟机自身的一部分,用于加载<JAVA_HOME>/lib核心类库与-Xbootclasspath参数指定的jar包到内存。无父类加载器。

扩展类加载器

用于加载<JAVA_HOME>/lib/ext目录下的类库或者由java.ext.dirs系统变量指定的类库。父类加载器为null。

应用程序(系统)类加载器

用于加载 classpath 上指定的类库。系统默认类加载器。父类加载器为扩展类加载器。

自定义加载器

父类加载器为系统类加载器,一般通过继承 URLClassLoader 来实现。

注意:分类中提及的父类加载器并非继承关系,而是给定一个parent变量来指定。

双亲委派模型

在这里插入图片描述
工作机制:如果一个类加载器收到类的加载请求,它并不会自己去加载,而是把请求委托给父类的加载器执行,如果还存在父类,则继续递归向上委托;如果父类无法完成加载任务,子加载器才会自己尝试去加载。

优势

  1. 使得Java类随着其类加载器一起具有了优先级的层次关系,避免类的重复加载引起的程序混乱
  2. 在与核心类重名的情况下优先选择核心类,由于只需要加载一次,在收到与核心API库重名的类时直接返回原始加载的核心API,防止核心API库被随意篡改

JVM两个class对象是否为同一个类对象的必要条件

  1. 类的完整类名必须一致,包括包名;
  2. 加载类的ClassLoader实例对象必须相同。

自定义类加载器

自定义类加载器,只需要继承ClassLoader抽象类或者URLClassLoader类,并重写findClass方法。(如果要打破双亲委派模型,则需要重写loadClass方法

// 自定义类加载器
public class CustomClassLoader  extends ClassLoader{
    private String classPath;

    public CustomClassLoader(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){
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    /**
     * 用于获取.class 的字节流
     * @param name
     * @return
     * @throws Exception
     */
    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;
    }

    public static void main(String[] args) throws Exception{
        // 自定义加载的位置,使用自定义类加载器加载
        CustomClassLoader loader = new CustomClassLoader("G:/tmp");
        // 注意包名带来的影响
        Class clazz = Class.forName("Car", true, loader);
        Object o = clazz.newInstance();

        Method m = clazz.getMethod("Print",null);
        m.invoke(o, null);
    }
}
// 注意该文件的位置在G:/tmp目录下
public class Car {

    public Car() {
        System.out.println("Car:" + getClass().getClassLoader());
        System.out.println("Car Parent:" + getClass().getClassLoader().getParent());
    }

    public String print() {
        System.out.println("Car:print()");
        return "carPrint";
    }
 }

在使用自定义类加载器之前,先使用javac命令获取Car.class文件(位置也是G:/tmp目录下),再使用自定义类加载器对得到的Car.class文件进行加载。

应用

  1. Tomcat自定义类加载器:
    自定义类加载器,以便对应用的不同版本类库进行隔离,相同版本进行共享;同时,对应用的类库与容器的类库进行隔离。
    对于非.class文件,需要转化为Java类时,就需要自定义类加载器。(JSP文件)
  2. 对Java核心代码加密:
    对.class文件加密,在自定义类加载器中完成解密;

参考

  1. https://blog.youkuaiyun.com/javazejian/article/details/73413292
  2. https://juejin.cn/post/6844903564804882445
  3. https://zhuanlan.zhihu.com/p/33509426
  4. https://segmentfault.com/a/1190000012925715
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值