(一)Java的类加载机制

本文深入讲解Java类加载机制,包括类加载的过程、时机及双亲委派模型的好处。阐述了从Java源码编译到字节码文件,再到JVM加载、验证、准备、解析和初始化的全过程。

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

类加载机制

虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。在 Java 语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。

1.Java源码的编译过程
  • 我们通过eclipse写一段代码,不调试和运行,把它保存起来,会变成一个后缀为 .java 的文件,我们把它叫做源文件,里面的东西就我们自己写的能看懂的代码。
  • 我们都知道C语言的源文件,要经过编译,才可以生成一个后缀为 .exe 的可执行文件。Java和C的道理是一样的,只不过Java的源文件经过编译后,会生成一个后缀为 .class 的文件,这些 .class文件很明显是不能直接运行的,它不像C语言(编译cpp后生成exe文件直接运行),这些 .class文件是交由JVM来解析运行。
  • JVM是运行在操作系统之上的,每个操作系统的指令是不同的,而JDK是区分操作系统的,只要你的本地系统装了JDK,这个JDK就是能够和当前系统兼容的。而class字节码运行在JVM之上,所以不用关心class字节码是在哪个操作系统编译的,只要符合JVM规范,那么,这个字节码文件就是可运行的。所以Java就做到了跨平台---->一次编译,到处运行!
  • 编译完的 .class 文件,虚拟机就要对其进行加载。
2.类的加载时机
  • Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
  • 虚拟机第一次使用到某个类时,需要对这个类的信息进行加载。一个类只会加载一次,之后这个类的信息放在堆空间,静态属性放在方法区。
3.类的加载过程

在这里插入图片描述
类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束。过程共有七个阶段,其中到初始化之前的都是属于类加载的部分。当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、连接、初始化三个步骤来对该类进行初始化,如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。

  • 加载

    1)通过一个类的全限定名来获取定义次类的二进制流(ZIP 包、网络、运算生成、JSP 生成、数据库读取)
    2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法去这个类的各种数据的访问入口。

  • 验证——是连接的第一步,确保 Class 文件的字节流中包含的信息符合当前虚拟机要求

    • 文件格式验证——只有通过这个阶段的验证后,字节流才会进入内存的方法区进行存储,所以后面 3 个验证阶段全部是基于方法区的存储结构进行的,不再直接操作字节流。

      1>. 是否以魔数 0xCAFEBABE 开头
      2>. 主、次版本号是否在当前虚拟机处理范围之内
      3>. 常量池的常量是否有不被支持常量的类型(检查常量 tag 标志)
      4>. 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
      5>. CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 编码的数据
      6>. Class 文件中各个部分集文件本身是否有被删除的附加的其他信息

    • 元数据验证——这一阶段主要是对类的元数据信息进行语义校验,保证不存在不符合 Java 语言规范的元数据信息。

      1>. 这个类是否有父类(除 java.lang.Object 之外)
      2>. 这个类的父类是否继承了不允许被继承的类(final 修饰的类)
      3>. 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
      4>. 类中的字段、方法是否与父类产生矛盾(覆盖父类 final 字段、出现不符合规范的重载)

    • 字节码验证——这是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段对类的方法体进行校验分析,保证校验类的方法在运行时不会做出危害虚拟机安全的事件。

      1>. 保证任意时刻操作数栈的数据类型与指令代码序列都鞥配合工作(不会出现按照 long 类型读一个 int 型数据)
      2>. 保证跳转指令不会跳转到方法体以外的字节码指令上
      3>. 保证方法体中的类型转换是有效的(子类对象赋值给父类数据类型是安全的,反过来不合法的)
      4>. ……

    • 符号引用验证——最后一个阶段的校验发生在迅疾将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,还有以上提及的内容。符号引用的目的是确保解析动作能正常执行,如果无法通过符号引用验证将抛出一个 java.lang.IncompatibleClass.ChangeError 异常的子类。如 java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError 等。

      1>. 符号引用中通过字符创描述的全限定名是否能找到对应的类
      2>. 在指定类中是否存在符方法的字段描述符以及简单名称所描述的方法和字段
      3>. 符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问
      ……

  • 准备——这个阶段正式为类变量(被static修饰的变量)分配内存并设置类变量初始值,这个内存分配是发生在方法区中。

    1)注意这里并没有对实例变量进行内存分配,实例变量将会在对象实例化时随着对象一起分配在JAVA堆中。
    2)这里设置的初始值,通常是指数据类型的零值。

private static int a = 3;

这个类变量a在准备阶段后的值是0,将3赋值给变量a是发生在初始化阶段。

  • 解析——这个阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
    1)符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量。
    2)直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和迅疾的内存布局实现有关

    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行,分别对应于常量池的 7 中常量类型。

  • 初始化——类初始化阶段是类加载过程的最后阶段,在这个阶段,java虚拟机才真正开始执行类定义中的java程序代码。
    在以下情况下,虚拟机会对一个类初始化:

    1)创建类实例的时候,分别有:a、使用new关键字创建实例;b、通过反射创建实例;c、通过反序列化方式创建实例。

    2)调用某个类的类方法(静态方法)

    3)访问某个类或接口的类变量,或为该类变量赋值。

    4)初始化某个类的子类。当初始化子类的时候,该子类的所有父类都会被初始化。

    5)直接使用java.exe命令来运行某个主类。

4.类以双亲委派模型加载,好处是什么?

JVM类加载器从上到下一共分为三类

  1. 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。

  2. 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。

  3. 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
    在这里插入图片描述

当一个加载器不管是应用程序类加载器还是我们自定义的类加载器在进行类加载的时候它首先不会自己去加载,它首先会把加载任务委派给自己的父类加载器,比如现在有个类需要我们的自定义类加载器来加载,其实它首先会把它交给应用程序类加载器,应用程序类加载器又会把任务交给扩展类加载器,一直往上提交,直到启动类加载器。启动类加载器如果在自己的扫描范围内能找到类,它就会去加载,如果它找不到,它就会交给它的下一级子加载器去加载,以此类推,这就是双亲委派模型。

为什么jdk里要提出双亲委派模型?

可以保证我们的类有一个合适的优先级,例如Object类,它是我们系统中所有类的根类,采用双亲委派模型以后,不管是哪个类加载器来加载Object类,哪怕这个加载器是自定义类加载器,通过双亲委派模型,最终都是由启动类加载器去加载的,这样就可以保证Object这个类在程序的各个类加载器环境中都是同一个类。在虚拟机里觉得一个类是不是唯一有两个因素,第一个就是这个类本身,第二个就是加载这个类的类加载器,如果同一个类由不同的类加载器去加载,在虚拟机看来,这两个类是不同的类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值