首页 > 编程语言 >Java类的加载

Java类的加载

时间:2024-11-14 15:20:09浏览次数:3  
标签:Java 字节 MyClass classData new Class 加载

       Java类加载的过程可以细分为四个主要阶段:加载(Loading)、验证(Verification)、准备(Preparation)和初始化(Initialization)。每个阶段都有其特定的任务和目的。

1. 加载(Loading)

       加载是类加载的第一步,它负责将类的二进制数据读入JVM内存,并转换为运行时数据结构。加载过程包括:

  • 通过类的全限定名获取定义此类的二进制字节流:这个字节流可以从文件系统、网络、数据库或者其他地方获取。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构:这一步涉及创建类的运行时表示,比如方法表、字段表等。
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口:这个 Class 对象是反射的基础,也是JVM内部用来管理类的重要对象。

       在加载阶段,JVM会检查是否已经加载过这个类,如果已经加载过,就不再重新加载,直接返回已有的 Class 对象。

2. 链接(Linking)

    链接阶段分为三个小步骤:验证、准备和解析。

  • 验证(Verification)

    • 文件格式验证:确保类文件遵循Java类文件格式。
    • 元数据验证:检查类的元数据信息是否正确,比如类是否有合法的父类,是否实现了所有抽象方法等。
    • 字节码验证:确保字节码不会导致任何安全问题,比如非法数据转换、非法跳转等。
    • 符号引用验证:确保类中引用的其他类、字段和方法确实存在。
  • 准备(Preparation)

    • 为类的静态变量分配内存,并设置默认初始值。对于基本类型,会设置为零值(如 int 为 0,boolean 为 false);对于引用类型,会设置为 null
  • 解析(Resolution)

    • 将类、接口、字段和方法的符号引用转换为直接引用。符号引用是在编译时生成的,而直接引用是在运行时解析得到的。
3. 初始化(Initialization)

    初始化阶段是类加载的最后一步,它包括执行类的初始化代码。这包括:

  • 执行类的静态变量赋值语句
  • 执行静态代码块

       初始化阶段是按需发生的,只有在真正使用到类的时候才会进行初始化。初始化阶段确保类的静态成员被正确地赋值和初始化。

触发类加载的情况

类加载可以分为显式和隐式两种情况:

显式加载
  • 使用 Class.forName 方法

    Class<?> myClass = Class.forName("com.example.MyClass");

    这里,"com.example.MyClass" 是类名,它被传递给默认的应用程序类加载器,后者会尝试加载这个类。

  • 类加载器对象可以调用 loadClass 方法来加载类

    ClassLoader loader = new MyClassLoader();
    Class<?> myClass = loader.loadClass("com.example.MyClass");

    类名同样是直接作为参数提供的。

  • 在自定义类加载器中,通常会重写 findClass 方法来实现具体的加载逻辑

    public class MyClassLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classData = loadClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                return defineClass(name, classData, 0, classData.length);
            }
        }
    
        private byte[] loadClassData(String className) {
            // 从特定位置加载类的字节码
            // 例如从文件系统、网络或其他来源
            return new byte[0]; // 返回加载的字节数组
        }
    }
隐式加载
  • 创建类的实例

    MyClass obj = new MyClass();

    这里不需要直接提供类名,因为JVM能从 new 表达式中推断出类名。

  • 访问静态成员或调用静态方法

    int value = MyClass.STATIC_FIELD;

    在这种情况下,MyClass 会被加载。

  • 初始化子类

    SubClass sub = new SubClass();

    即使 SubClass 直接被创建,JVM也会加载 SubClass 及其所有的超类。

  • 使用反射API

    Method method = MyClass.class.getMethod("someMethod", String.class);

    如果 MyClassString 类尚未被加载,JVM会先加载它们。

  • 接口实现

    List<String> list = new ArrayList<>();

    这里 ListArrayList 都会被加载。

  • 服务提供者加载: Java服务提供者框架允许通过配置文件来指定服务提供者类。例如,在 META-INF/services/com.example.Service 文件中指定了提供者类名,当程序请求服务时,JVM会加载这些类。

  • 动态代理

    InvocationHandler handler = ...;
    MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(
        MyInterface.class.getClassLoader(),
        new Class[] { MyInterface.class },
        handler
    );

    这里 MyInterface 会被加载。

双亲委派模型

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

       这种模型的好处在于确保了类的唯一性,避免了不同类加载器加载同一个类的多个版本,从而提高了安全性。

自定义类加载器

       如果需要特殊的类加载行为,比如从网络加载类、加密/解密类文件等,可以创建自定义类加载器。自定义类加载器通常继承自 java.lang.ClassLoader,并重写 findClass 方法来实现具体的加载逻辑。

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String className) {
        // 从特定位置加载类的字节码
        // 例如从文件系统、网络或其他来源
        return new byte[0]; // 返回加载的字节数组
    }
}

       通过上述步骤,你可以详细了解类加载的整个过程,包括类加载器如何获得类名、类加载的具体步骤以及双亲委派模型的工作方式。

标签:Java,字节,MyClass,classData,new,Class,加载
From: https://blog.csdn.net/m0_50742275/article/details/143662373

相关文章

  • Java线程的sleep和wait的区别
        在Java中,Thread.sleep()和Object.wait()都可以让线程暂停执行,但是它们的作用机制和使用场景是不同的。下面是这两个方法的主要区别:Thread.sleep(longmillis,intnanos)参数:millis 是毫秒数,nanos 是额外的纳秒数(0到999,999之间)。行为:当前线程将暂停执行至少......
  • 26届JAVA 学习日记——Day8
    2024.11.12周二距离上次打卡已经过去了三天,虽然有三天没有学习,但是旅游的过程还是很治愈的。今天开始继续打卡。八股SpringBoot里面有哪些重要的注解?有一个配置相关的注解是哪个?@SpringBootApplicaiton:用于标注主应用程序类,标识一个SpringBoot应用程序的入口点,同时启......
  • Idea2024-java-Maven开发配置
    断断续续用过Idea做一些java的学习,在此记录一下最后的配置过程。安装idea,社区版https://www.jetbrains.com.cn/idea/download/?section=windows安装后,setting检查maven的配置 如果想换Mavan的版本,可以自己下载到本地后,点击“Mavanhomepath”后面的"..."选择你下载并解压后......
  • 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-4-启动浏览器-基于Maven(详细教程)
    1.简介上一篇文章,宏哥已经在搭建的java项目环境中添加jar包实践了如何启动浏览器,今天就在基于maven项目的环境中给小伙伴们或者童鞋们演示一下如何启动浏览器。2.eclipse中新建maven项目1.依次点击eclipse的file-new-other,如下图所示:2.在搜索框输入关键字“maven”,然后......
  • Java复习45(PTA)
    office文档页码打印分数20全屏浏览切换布局作者 黄敏单位 河北科技大学在office软件(word,excel)中,有时只需要打印整个文档中的一部分,就需要用户选择需要打印的页码范围。目前输入的页码范围格式定义为:以逗号分割,可以使用-表示连续页码。例如:1,3,5-9,20。表示需要打印......
  • hnuJava程序设计基础训练-2024
    1. DNA序列(Java)【问题描述】 一个DNA序列由A/C/G/T四个字母的排列组合组成。G和C的比例(定义为GC-Ratio)是序列中G和C两个字母的总的出现次数除以总的字母数目(也就是序列长度)。在基因工程中,这个比例非常重要。因为高的GC-Ratio可能是基因的起始点。给定一个很长的DNA序列,以......
  • Java复习44(PTA)
     单词替换分数10全屏浏览切换布局作者 孙晨霞单位 河北农业大学设计一个对字符串中的单词查找替换方法,实现对英文字符串中所有待替换单词的查找与替换。输入格式:首行输入母字符串,第二行输入查询的单词,第三行输入替换后的单词。输出格式:完成查找替换后的完整......
  • Java流程控制(三.一)
    publicclassoperator{publicstaticvoidmain(String[]args){//打印九九乘法表for(inti=1;i<10;i++){for(intj=1;j<=i;j++){System.out.print(i+"*"+j+"="+i*j+"\t&q......
  • Java基础面试题
    1.switch的使用intx=2;intresult=0;switch(x){case1:result=result+x;case2:result=result+x*2;case3:result=result+x*3;}System.out.println(result);//返回结果为10//switch语句当case与值不......
  • Java8
    Lambda一、简介Lambda表达式是Java8引入的一种新的语法,它允许你以简洁的方式表示可传递给方法或存储在变量中的代码块。Lambda表达式可以用来替代匿名内部类,使代码更加简洁、易读和易于维护。二、Lambda表达式的基本语法Lambda表达式的语法形式为:(parameters)->expre......