一篇文章弄懂JVM类加载机制过程以及原理

那么让我们来详细讲解一下“一篇文章弄懂JVM类加载机制过程以及原理”的完整攻略。

1. JVM类加载机制基础

在深入了解JVM类加载机制的过程之前,我们需要先了解一些相关的基础知识。

1.1 类、类加载器和类加载

Java中,我们通常所说的类是指Java类,而Java类的定义是以.java文件为载体,通过编译器(如javac)将其转换为.class文件后生成,.class文件就是Java类的二进制字节码形式。Java虚拟机通过字节码来执行Java程序,因此Java虚拟机加载的是.class文件。

类加载器是Java虚拟机的组成部分之一,用于把类加载到内存中并生成Java类对象。一个Java类在被加载到内存中之前,需要经过三个步骤:加载、链接和初始化。其中加载即是将Java类的字节码文件读取到内存中,并创建一个对应的java.lang.Class对象的过程。

1.2 类的生命周期

一个Java类从被加载到虚拟机内存中开始,到虚拟机将其卸载出内存为止,其整个生命周期可以划分为如下7个阶段:

  1. 加载:将类的.class文件读入内存,并在堆空间创建一个用于描述类的java.lang.Class对象。
  2. 验证:验证类的字节码文件的正确性,包括文件格式、语义、二进制兼容性等。
  3. 准备:为类的静态变量分配内存,并设置默认值。
  4. 解析:将常量池中的符号引用转换为直接引用,即将常量池中的类名、方法名、字段名等转换为内存地址。
  5. 初始化:为类的静态变量赋值,并执行静态代码块。
  6. 使用:类从虚拟机中加载后,才能被Java程序调用使用。
  7. 卸载:类在虚拟机内不再使用,卸载类并释放内存。

1.3 类加载器分类

Java虚拟机的类加载器有三种,按照类加载器的启动顺序可分为以下三类:

  1. 启动类加载器(Bootstrap ClassLoader):用于加载Java虚拟机核心库中的类,如rt.jarresources.jar等。
  2. 扩展类加载器(Extension ClassLoader):用来加载Java虚拟机扩展目录中的类库,即$JAVA_HOME/jre/lib/ext目录下的所有jar包。
  3. 应用程序类加载器(Application ClassLoader):又称为系统类加载器,用于加载当前应用程序类路径(即CLASSPATH)下的全部类库。

1.4 双亲委派模型

Java虚拟机的类加载器采用了一种父亲委派的加载机制,即双亲委派模型。当一个类被加载到虚拟机中时,会先从启动类加载器开始依次向下搜索,直到找到该类为止。如果找不到,则会抛出ClassNotFoundException异常。

具体来说,类加载器在加载一个类时,首先会尝试使用其父亲类加载器去加载该类,只有在父亲类加载器无法加载该类时才会尝试自己加载该类。这样做的好处是防止重复加载,提高安全性和系统稳定性。

在实际应用中,一般自定义类加载器都是基于父类加载器进行构建的,而双亲委派模型也是Java虚拟机对于Java类的安全保障机制之一。

2. JVM类加载机制过程

了解了上述的基础知识之后,我们可以开始深入了解JVM类加载机制的过程。

2.1 类加载的过程

类加载的过程是在“使用”阶段之前,即“初始化”阶段。在“初始化”阶段,Java虚拟机才会真正执行类的初始化,也才算是对该类的主动使用。

类加载的过程主要分为以下三个阶段:

  1. 加载:从文件系统或网络中读入字节码流,将其转换成java.lang.Class对象。
  2. 链接:将Java类的.class文件中的符号引用(如类和接口的全限定名、字段和方法的名称等)解析为直接引用(如在运行时内存中的对象、方法区中的指针等),并把刚刚加载的.class文件中的变量初始化为默认值(如0或null)。
  3. 初始化:执行类的初始化代码,包括静态初始化代码块和变量初始化等。当一个类被初始化时,依次执行此类父类的静态代码块和构造方法。

2.2 类加载器的实现过程

Java虚拟机在加载Java类时,会使用Java类加载机制来完成下面的三个过程:

  1. 将Java类的字节码文件加载到Java虚拟机内存中:每个Java类都有一个与之对应的java.lang.Class对象,该对象记录了这个类的完整信息。
  2. 验证Java类的字节码文件的正确性:验证阶段需要检查字节码文件的文件格式(Magic Number)、语义合法性和二进制兼容性等。
  3. 让内存中的Java类“可用”:包括初始化类的数据和代码,初始化阶段主要是执行类的静态初始化代码块和静态变量初始化。为了实现这些操作,Java虚拟机需要使用到类加载器。

类加载器在加载Java类的过程中,通常会遵循如下流程:

  1. 检查类是否已经被加载过,如果已经被加载过,则直接返回。
  2. 如果类没有被加载过,则让其父加载器(除了启动类加载器)试图加载该类。
  3. 如果父加载器无法加载该类,则调用自己的加载方法进行加载。

一个类的加载过程可以简单地概括为:类加载器在加载某个类时,首先会代理向其父类加载器发送加载请求,以此类推,直到请求传递到最顶层,即启动类加载器。如果最顶层的类加载器可以找到该类,则将该类加载到内存中;如果所有的类加载器都无法找到该类,则会抛出ClassNotFoundException异常。

2.3 类的双亲委派机制

Java虚拟机的类加载器采用了双亲委派机制。当Java虚拟机在加载某个类时,会依次向其父类加载器发送加载请求,直到顶级类加载器——启动类加载器。如果启动类加载器也无法找到该类,则会抛出ClassNotFoundException异常。

采用双亲委派机制的好处在于:

  • 可以防止类的重复加载:当某个类在某个类加载器的加载范围内已经被加载,其他类加载器如果需要加载该类,只需要将该请求委托给其父类加载器进行处理即可。
  • 可以保证Java核心类的安全:Java核心类库都是由启动类加载器加载,而启动类加载器是由Java虚拟机实现的,因此任何不受信任的代码都无法通过其父加载器加载核心类库,从而保证了Java核心类的安全性。

3. 示例说明

下面通过两个例子来帮助更好地理解JVM类加载机制的过程。

示例1

假设有如下Java代码:

public class Test {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

当我们编译运行该代码时,Java虚拟机需要先加载该类,然后才能开始执行。

在执行Java代码时,Java虚拟机首先需要将Java类的.class文件读入内存,并在堆空间创建一个java.lang.Class对象。在进行类加载的过程中,Java虚拟机采用了双亲委派机制。具体来说,Java虚拟机会按照以下的顺序进行类加载:

  1. 首先,Java虚拟机会由应用程序类加载器(也叫系统类加载器)尝试加载该类,但该类不在应用程序类加载器的加载范围内。
  2. 随后,应用程序类加载器会代理向扩展类加载器发送加载请求,扩展类加载器无法加载该类,因此继续向其父加载器——启动类加载器发送加载请求。
  3. 启动类加载器成功加载该类。

最终,Java虚拟机将该类加载到内存中,并创建一个对应的java.lang.Class对象,该对象用于描述该类的完整信息。

示例2

假设我们需要自定义一个类加载器,并使用其去加载一个类。我们可以在自定义类加载器中重写findClass方法,并将该类放在指定的Class路径下:

public class MyClassLoader extends ClassLoader {

    /**
     * 定义加载的目录
     */
    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    /**
     * 重写findClass方法
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 将类名转换为对应的文件路径
        String fileName = name.replace(".", "/");
        // 拼接完整的文件路径
        String filePath = classPath + "/" + fileName + ".class";
        // 读取字节码文件到内存中
        byte[] classBytes = loadClassBytes(filePath);
        if (classBytes == null) {
            throw new ClassNotFoundException("Cannot load class " + name);
        }
        // 将字节码转换为Class对象
        return defineClass(name, classBytes, 0, classBytes.length);
    }

    /**
     * 读取字节码文件到内存中
     */
    private byte[] loadClassBytes(String fileName) {
        // 从文件中读取字节码文件
        // ...
    }
}

我们可以使用以下代码来测试该类加载器:

public static void main(String[] args) throws Exception {
    // 创建自定义类加载器
    MyClassLoader myClassLoader = new MyClassLoader("/path/to/class");
    // 在自定义类加载器中载入某个类
    Class<?> clazz = myClassLoader.loadClass("com.example.MyClass");
    // 创建该类的实例
    Object instance = clazz.newInstance();
    // 调用该类的某个方法
    Method method = clazz.getMethod("xxx", String.class);
    method.invoke(instance, "Hello, World!");
}

在这个例子中,我们自定义了一个类加载器,并将其用于载入一个指定目录下的类。类加载器会先尝试使用父类加载器加载该类,如果父类加载器无法加载,则代理调用自己的类加载方法进行加载。

值得注意的是,在本例中,我们自定义了一个类加载器,并将字节码文件读入到内存中。在实际应用中,JVM类加载机制也是如此,Java虚拟机将Java类的字节码文件读取到内存中,并创建一个对应的java.lang.Class对象。类加载器的作用在于将Java类加载到内存中,并生成对应的Class对象。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一篇文章弄懂JVM类加载机制过程以及原理 - Python技术站

(0)
上一篇 2023年5月26日
下一篇 2023年5月26日

相关文章

  • JSONObject按put顺序排放与输出方式

    下面是有关”JSONObject按put顺序排放与输出方式”的攻略。 什么是JSONObject JSONObject是Java中的一个类,可以用于存储和操作JSON格式的数据。它能够将Java对象转换成JSON格式的字符串,也可以将JSON格式的字符串转换成Java对象。 JSONObject按put顺序排放 JSONObject是一种无序的数据结构,它并…

    Java 2023年5月26日
    00
  • spring boot 2.x静态资源会被拦截器拦截的原因分析及解决

    一、问题描述 在使用Spring Boot 2.x开发项目时,我们可能会遇到一个问题,即静态资源(如CSS、JS、图片等)会被拦截器拦截而无法正常加载导致页面样式、交互等异常。这是因为Spring Boot 2.x采用了不同于之前版本的WebMvcConfigurerAdapter配置方式,在配置拦截器时需要特别注意。 二、原因分析 在Spring Boot…

    Java 2023年5月20日
    00
  • Maven分模块开发执行指令失败的问题

    Maven分模块开发是一种常见的软件开发方法,但在进行模块执行指令时,有时会遇到执行失败的问题。本攻略旨在帮助开发人员解决Maven分模块开发执行指令失败的问题,步骤如下: 一、检查pom.xml文件配置 在进行Maven分模块开发时,每个子模块都有自己的pom.xml文件。执行指令失败时,首先需要检查各个子模块的pom.xml文件是否正确配置。特别要注意以…

    Java 2023年5月19日
    00
  • java实现的n*n矩阵求值及求逆矩阵算法示例

    1. 求矩阵的值 求n*n矩阵的值,需要使用行列式的计算方法,具体算法如下: 当矩阵为1*1时,其值即为该矩阵中的元素。 当矩阵为22时,其值为:a[1][1]a[2][2]-a[1][2]*a[2][1]。 当矩阵为n*n时,其值为:按矩阵的第一行、第一列、第二行、第二列等开始,按矩阵的行列式展开来求得。 具体来说,可以使用递归算法来求解矩阵的行列式。递归…

    Java 2023年5月19日
    00
  • 关于Java多线程上下文切换的总结

    下面是我对“关于Java多线程上下文切换的总结”这个话题的详细讲解: 简介 Java中的多线程机制可以实现并发执行,提高系统的吞吐量和效率。但是多线程机制也有它的弊端,例如上下文切换会给系统带来额外的开销。因此了解多线程上下文切换的机制对于Java程序员来说是非常重要的。 上下文(Context)切换 上下文切换是指当进程或线程需要访问一个未在当前内存中的资…

    Java 2023年5月18日
    00
  • Java连接Sql数据库经常用到的操作

    Java连接Sql数据库操作攻略 Java连接Sql数据库操作可以分为以下几个步骤: 加载数据库驱动 连接数据库 创建Statement对象 执行SQL语句 处理结果 下面详细讲解这几个步骤。 1. 加载数据库驱动 在连接Sql数据库之前,首先需要加载数据库驱动。例如,如果想要连接MySQL数据库,可以使用以下代码加载驱动: Class.forName(&q…

    Java 2023年6月1日
    00
  • Java基础之Stream流原理与用法详解

    Java基础之Stream流原理与用法详解 1. 什么是Stream流? Stream流是Java 8中引入的一种新的API,它允许我们在集合上进行的函数式操作。它使我们能够以声明式方式处理集合中的元素,而不是直接以循环形式迭代它们。在Java 8之前,Collections类提供了大量用于操作集合的方法。但是,为了使用这些方法,你必须在代码中写出来循环,这…

    Java 2023年5月27日
    00
  • Java使用DateUtils对日期进行数学运算经典应用示例【附DateUtils相关包文件下载】

    我来为你详细讲解“Java使用DateUtils对日期进行数学运算经典应用示例”。 1. DateUtils是什么? DateUtils是Apache Commons Lang包中提供的一个工具类,提供了许多有用的方法来操作日期。 在Java中,使用Date类来表示日期和时间,但是其提供的接口较为简单,如想要对日期进行格式化,进行日期计算,获得某个日期的月份…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部