服务器测评网
我们一直在努力

java自己的类怎么加载

Java类加载机制是Java虚拟机的核心组成部分,它负责将描述类的数据从Class文件加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的Java类型,理解Java自身的类加载过程,对于深入掌握Java底层原理、解决类加载冲突、实现热部署等场景至关重要。

java自己的类怎么加载

类加载的基本概念

类加载是指Java虚拟机把描述类的数据从Class文件(或网络、动态生成等来源)加载到内存,并对数据进行校验、转换、解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程,Java类加载遵循“按需加载”原则,即只有当类首次被主动使用时(如创建实例、访问静态字段、调用静态方法等),才会触发加载。

类加载的过程分为五个阶段:加载、验证、准备、解析、初始化,加载、验证、准备、解析统称为链接阶段,每个阶段都有明确的任务,共同确保类加载的正确性和安全性。

Java内置的类加载器体系

Java提供了三种类加载器,它们以树形结构协同工作,形成了类加载器的层次体系:

启动类加载器(Bootstrap ClassLoader)

启动类加载器是Java虚拟机的一部分,由C++实现(HotSpot虚拟机中),负责加载存放在<JAVA_HOME>/jre/lib目录下的核心类库,如rt.jar(Java基础类库)、resources.jar等,启动类加载器没有父加载器,在类加载器层次结构中处于最顶层,由于它由虚拟机自身实现,开发者无法直接获取其引用,例如通过ClassLoader.getSystemClassLoader().getParent()返回的会是null

扩展类加载器(Extension ClassLoader)

扩展类加载器由Java语言实现,继承自URLClassLoader,负责加载<JAVA_HOME>/jre/lib/ext目录下的扩展类库,或通过java.ext.dirs系统属性指定的路径中的类库,它的父加载器是启动类加载器,开发者可以通过ExtClassLoader类(sun.misc.Launcher$ExtClassLoader)获取其实例,但通常不建议直接操作,而是通过ClassLoader.getExtClassLoader()方法间接使用。

应用程序类加载器(Application ClassLoader)

应用程序类加载器也称为系统类加载器,由Java语言实现,负责加载用户类路径(ClassPath)上指定的类库,它是程序中默认的类加载器,例如通过new MyClass()创建的类,就是由应用程序类加载器加载的,开发者可以通过ClassLoader.getSystemClassLoader()获取其实例,这也是开发中最常接触到的类加载器。

类加载的生命周期

加载阶段

加载阶段是类加载的第一个阶段,主要完成三件事:

  • 通过类的全限定名获取定义此类的二进制字节流(如从Class文件、网络、动态代理生成等);
  • 将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  • 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中,Class对象则作为访问方法区数据的入口被引用。

java自己的类怎么加载

链接阶段

链接阶段分为验证、准备、解析三个子阶段:

  • 验证:确保加载的Class文件符合当前虚拟机的要求,不会危害虚拟机安全,验证阶段包括文件格式验证(是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机处理范围内)、元数据验证(字节码描述的信息是否符合Java语言规范)、字节码验证(程序语义是否合法、跳转指令是否指向正确位置)和符号引用验证(符号引用中通过字符串描述的全限定名是否能找到对应的类)。
  • 准备:为类的静态变量分配内存,并设置初始值,这里的“初始值”指的是数据类型的零值,例如int类型的静态变量会被赋值为0boolean类型为false,引用类型为null,注意,与代码中显式赋的值不同,显式赋值会在初始化阶段完成。
  • 解析:将常量池内的符号引用替换为直接引用,符号引用是一组符号来描述所引用的目标,例如类的全限定名、字段的名称和描述符等;直接引用可以是直接指向目标的指针、相对偏移量或能间接定位到目标的句柄,解析阶段会确保符号引用能正确转换为直接引用。

初始化阶段

初始化阶段是类加载的最后阶段,执行的是类构造器<clinit>()方法。<clinit>()方法由编译器自动收集类中的所有静态变量的赋值动作和静态语句块(static{}块)中的语句合并产生,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块只能访问定义在静态语句块之前的变量,定义在它之后的变量可以赋值,但不能访问。

<clinit>()方法与实例构造器<init>()不同,它不需要显式调用虚拟机保证在子类类加载器之前执行,且虚拟机会保证在多线程环境中<clinit>()方法的正确加锁和同步,如果多个线程同时去初始化一个类,只会有一个线程执行<clinit>()方法,其他线程需要阻塞等待。

双亲委派模型:Java类加载的核心机制

Java的类加载器之间通过“双亲委派模型”(Parent Delegation Model)协同工作,该模型要求除了顶层的启动类加载器外,其余类加载器都应当有自己的父加载器,这里“父子关系”通过组合(Composition)而非继承实现。

当一个类加载器收到类加载请求时,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器完成,每一层类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成加载请求(在它的搜索范围内没有找到所需的类)时,子加载器才会尝试自己去加载。

双亲委派模型的优势在于:

  • 安全性:通过委派给父加载器,避免核心类库被恶意替换,如果用户自定义了一个java.lang.String类,由于双亲委派模型的存在,加载请求会先委派给启动类加载器,而启动类加载器已经加载了核心库中的String类,因此用户自定义的String类不会被加载,保证了Java核心类的安全性。
  • 避免重复加载:通过委派机制,不同类加载器不会重复加载同一个类,节省了内存空间。

双亲委派模型并非强制要求,开发者可以通过自定义类加载器并重写loadClass()方法来破坏双亲委派模型,常见的破坏场景包括:

  • 线程上下文类加载器:在JDBC、JNDI等服务中,父类加载器需要加载由子类加载器加载的类,此时通过线程上下文类加载器打破委派。
  • OSGi模块化框架:OSGi通过自定义类加载器实现模块间的隔离与动态加载,需要破坏双亲委派模型来满足模块依赖需求。

Java模块化系统对类加载的影响

Java 9引入了Java平台模块系统(JPMS,Project Jigsaw),对类加载机制进行了重构,在JPMS中,类加载不再仅基于类路径(ClassPath),而是基于模块(Module),每个模块可以明确声明它依赖的其他模块以及导出的包,非导出的包对其他模块不可见,这被称为“强封装特性”。

java自己的类怎么加载

模块化系统引入了三层类加载器层次:

  • Boot Layer:由启动类加载器加载,包含Java核心模块(如java.base)。
  • Platform Layer:由平台类加载器(Platform ClassLoader,取代了扩展类加载器)加载,包含平台模块(如java.sql)。
  • Application Layer:由应用程序类加载器加载,包含用户自定义模块。

模块化系统通过模块描述符(module-info.class)明确了模块间的依赖关系,进一步增强了类加载的安全性和可控性,但也使得类加载过程更加复杂。

自定义类加载器的实践与应用

除了Java内置的类加载器,开发者还可以通过继承java.lang.ClassLoader类来自定义类加载器,自定义类加载器通常用于以下场景:

  • 加载网络上的类文件(如分布式系统中的代码动态加载);
  • 加载加密的类文件(实现代码保护);
  • 实现类的热部署(在不重启应用的情况下更新类)。

自定义类加载器的核心步骤是重写findClass()方法(而非loadClass()方法,以避免破坏双亲委派模型),在findClass()方法中,需要实现类的查找(如从文件、网络获取字节流)和转换(将字节流转换为Class对象)。

public class CustomClassLoader extends ClassLoader {
    private String classPath;
    public CustomClassLoader(String classPath) {
        this.classPath = classPath;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, classData, 0, classData.length);
    }
    private byte[] loadClassData(String className) {
        // 从文件或网络读取字节流,此处省略实现
        return null;
    }
}

Java类加载机制是Java虚拟机的核心特性,它通过类加载器体系、双亲委派模型和严格的加载生命周期,确保了类的正确加载和安全运行,从早期的双亲委派模型到Java 9的模块化系统,类加载机制不断演进,以适应更复杂的应用场景。

对于开发者而言,深入理解类加载机制有助于解决类加载冲突、实现动态加载和热部署等高级功能,同时也能更好地编写安全、高效的Java程序,随着Java语言的持续发展,类加载机制可能会进一步优化,以支持更灵活的模块化管理和更高效的动态加载能力。

赞(0)
未经允许不得转载:好主机测评网 » java自己的类怎么加载