浅谈Java类的加载,链接及初始化

yizhihongxing

浅谈Java类的加载,链接及初始化

类加载的过程

类的加载是指将类的.class文件中的二进制数据读入到内存中,将其转化为方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,并向Java虚拟机注册,以便于该类被引用时能够找到它。

类加载的过程分为三个步骤:加载、链接和初始化。

加载

加载阶段是将类装载至内存并生成由JVM管理的Class对象。Java虚拟机规范并没有明确规定从哪里加载类,但是大多数的虚拟机实现都是从文件系统中加载类二进制数据,当然也可以从ZIP、JAR文件和网络中获取。加载完成后,系统会为这个类生成一个java.lang.Class对象,作为访问方法区中这些数据结构的入口。

具体实现:

  1. 通过一个类的全限定名(全限定名包括包名和类名)获取其定义的二进制字节流。
  2. 将这个字节流所代表的静态储存结构转化为方法区的运行时数据结构。
  3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为访问方法区中这些数据结构的入口。

链接

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

验证

验证阶段是保证被加载的类的正确性。验证可以分为文件格式验证、元数据验证、字节码验证、符号引用验证 4 个部分。

  1. 文件格式验证:保证展现数据的字节流符合 Class 文件格式的规范,并且能被当前运行的虚拟机理解。
  2. 元数据验证:对类的元数据信息进行语义分析的过程,以保证其符合 JVM 规范的要求。例如类访问修饰符的合法性,以及类中字段、方法描述符以及属性表等是否缺失。
  3. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。并确认程序中不会发生不被捕获的异常、变量的类型必须与指令能够产生的类型一致、跳转指令不能指向不良代码等。
  4. 符号引用验证:对类自身以外的信息进行匹配性检查,主要包括在类、字段、方法中使用的类描述符、字段描述符、方法描述符、方法句柄和调用点限定符的检查是否能够在当前程序执行环境下被解析。

准备

准备过程是为类的静态变量分配内存并设置默认值的过程。

具体实现:

每个字段都会有一个初始值。这里注意「字段初始值」和「默认值」是不同的。默认值是 Java 中变量声明时的初始值,而类变量(包括 static 变量和 static 块中的变量)的“默认值”是在“准备阶段”赋值的。

例如:

public class Test {
    private static int a = 1;
    private static final int b = 2;
}

以上代码对应的字节码文件,执行准备阶段时:

public class Test {
    private static int a; // 这里也可以设置为 0 
    private static final int b = 2;
}

这里最终 a 值不是 1,而是 0。因为它是一个类变量,在类加载的过程中,它的正确初始值是 0。

解析

解析阶段是将类中的符号引用转化为直接引用的过程。符号引用就是指向某个目标的符号,可以是类名、方法名、属性名。而直接引用就是指向目标的指针、相对偏移量或者句柄。将符号引用转化为直接引用可以理解为程序编译后就已经存在,需要在类加载阶段就完成映射。

初始化

初始化阶段是对类进行初始化,主要包括为类的静态变量赋值。

通俗的讲,只有在准备阶段中,类变量被赋予了系统推荐的初始值后,才会进入类的初始化阶段。类初始化阶段才真正开始执行类中定义的 Java 代码。

具体实现:

  1. 如果该类存在表示类初始化方法的类构造器 (),则执行该构造器。类构造器是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
  2. 类的初始化是同步进行的。随着线程的增加,也申请需要触发初始化的类的 Class 对象的锁,所以类初始化是线程安全的。
  3. 一个类被初始化后,就会进入“初始化完毕状态”,即不需要再次初始化。

示例说明

示例 1:加载与初始化顺序

public class LoadClass {
    static {
        System.out.println("这是静态代码块.");
    }

    public static void main(String[] args) {
        System.out.println("这是main方法.");
        System.out.println(A.name); // 输出 A
    }
}

public class A {
    public static String name = "A";
    static {
        System.out.println("这是A的静态代码块.");
    }
}

以上程序中,先有 LoadClass,它引入了 A,而 A 中定义了一个 static 的“变量 name”和一个静态代码块。因此,对于这段代码的执行顺序是:先执行 LoadClass 的静态代码块,再执行main方法,最后输出字符串 A。

示例 2:验证阶段

当 Java 类的字节码被加载到 Java 虚拟机中时,加载器会检查其字节码文件的格式是否正确,以及其他的二进制数据的正确性。如果字节码文件有问题,Java 虚拟机就会抛出 ClassFormatError 异常。

当 Java 类通过字节码文件验证阶段后,Java 虚拟机必须尽可能验证该类的元数据和字节码语义是否正确。如果 Java 类的字节码文件有使用 Java 虚拟机规范所禁止的操作、或者使用了不正确的数据,Java 虚拟机就会抛出 VerifyError 异常。下面是一个验证阶段的示例:

public class ValidateClassFileSample {
    public static void main(String[] args) {
        int x = 1;
        int y = 1;
        int z = x + y;
        System.out.println(z);
    }
}

编译以上代码后,删除 ValidateClassFileSample 中的 .class 文件,执行命令行 java ValidateClassFileSample 会抛出以下异常信息:

Exception in thread "main" java.lang.VerifyError: (class: ValidateClassFileSample, method: main signature: ([Ljava/lang/String;)V) Illegal target of jump or loop

这是因为编译器可能生成了同样的变量表、局部表和代码,但是它们可能引用不同的标签。一些字节码语句与标签不能对应,因此导致了 VerifyError。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:浅谈Java类的加载,链接及初始化 - Python技术站

(0)
上一篇 2023年6月20日
下一篇 2023年6月20日

相关文章

  • python 拷贝特定后缀名文件,并保留原始目录结构的实例

    Python拷贝特定后缀名文件,并保留原始目录结构的实例攻略 在Python中,你可以使用shutil模块来拷贝文件,并使用os模块来处理目录结构。下面是一个完整的攻略,详细讲解了如何拷贝特定后缀名的文件,并保留原始目录结构。 步骤1:导入必要的模块 首先,你需要导入shutil和os模块,以便使用它们的函数和方法。 import shutil import…

    other 2023年8月5日
    00
  • java如何实现嵌套对象转大map(扁平化)

    Java如何实现嵌套对象转大Map(扁平化) 在Java中,我们可以使用递归算法来实现嵌套对象转大Map,也称为扁平化。这个过程将嵌套的对象结构转换为一个扁平的键值对集合,其中每个键都是由嵌套的属性路径组成,而值则是对应的属性值。 下面是一个完整的攻略,包含了实现嵌套对象转大Map的步骤和两个示例说明。 步骤 创建一个空的Map对象,用于存储扁平化后的键值对…

    other 2023年7月28日
    00
  • MyBatis实现两种查询树形数据的方法详解(嵌套结果集和递归查询)

    MyBatis是一种优秀的ORM(对象关系映射)框架,它可以帮助我们更加方便地进行数据库操作。MyBatis不仅可以处理常规的查询操作,还可以处理一些比较复杂的场景,比如树形结构的数据查询。而在树形结构数据查询中,常用的方法有两种:嵌套结果集和递归查询。本文将详细讲解这两种方法的实现过程。 一、嵌套结果集实现树形结构数据查询 嵌套结果集是一种比较容易理解的方…

    other 2023年6月27日
    00
  • 关于python:安全地从字典中删除多个键

    以下是关于“关于python:安全地从字典中删除多个键”的完整攻略,包含两个示例。 关于python:安全地从字典中删除多个键 在Python中我们可以使用del语句字典中删除一个或多个键。但是,如果我们试从字典中删除不存在的键,将会引发KeyError异常。为了避免种情况,我们可以使用字典的pop()方法或item()方法来安全地删除多个键。下面我们将介绍…

    other 2023年5月9日
    00
  • Nacos配置的多文件加载与共享配置方式

    Nacos是阿里巴巴开源的一个服务发现和配置中心框架,支持多种配置格式和数据源,具有高可用和可扩展性。在使用Nacos进行配置管理时,可以通过多文件加载和共享配置方式实现更加灵活和高效的配置管理。 多文件加载 在Nacos中,可以将配置按照不同的属性隔离到不同的文件中,然后使用nacos-client来同时加载多个配置文件进行统一管理。 具体步骤如下: 创建…

    other 2023年6月25日
    00
  • yum安装指定版本的软件包的方法

    yum安装指定版本的软件包的方法 当我们需要安装某个软件包时,我们通常执行如下命令进行安装: yum install packagename 但是,如果我们需要安装某个特定版本的软件包,该怎么办呢? 下面介绍在yum中安装指定版本软件包的方法。 确定软件包版本号 首先,我们需要确定需要安装软件包的版本号。 例如,我们想要安装Nginx 1.18.0版本,则需…

    其他 2023年3月28日
    00
  • MyBatisCodeHelperPro插件下载及使用教程详解

    很抱歉,作为文本模型,我无法以标准的markdown格式回答您的问题。但是,我可以为您提供一份详细的攻略,包含MyBatisCodeHelperPro插件的下载和使用教程。以下是完整攻略: MyBatisCodeHelperPro插件下载及使用教程 下载插件 打开浏览器,访问MyBatisCodeHelperPro插件的官方网站。 在网站上找到下载链接,并点…

    other 2023年10月17日
    00
  • Python学习之名字,作用域,名字空间

    Python学习之名字、作用域、名字空间攻略 名字(Name) 在Python中,名字是用来标识变量、函数、类等对象的标识符。名字是区分不同对象的唯一标识符,可以通过名字来引用对象。 作用域(Scope) 作用域是指在程序中访问名字的有效范围。Python中有四种作用域:内置作用域(built-in scope)、全局作用域(global scope)、局部…

    other 2023年8月8日
    00
合作推广
合作推广
分享本页
返回顶部