一篇文章弄懂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日

相关文章

  • Spring框架学习之Cache抽象详解

    Spring框架学习之Cache抽象详解 什么是Cache抽象 Cache 抽象是 Spring 框架为了简化缓存的使用而提供的一种抽象层,它定义了 Spring 缓存的公共 API,并且提供了对不同缓存系统的支持。通过在 Cache 抽象上编程,应用程序开发人员可以将其应用程序代码与底层缓存实现解耦,从而使系统更加可维护和可扩展。 Cache 抽象的核心接…

    Java 2023年5月19日
    00
  • 使用Python脚本对Linux服务器进行监控的教程

    接下来我会详细讲解如何使用Python脚本对Linux服务器进行监控的完整攻略。 1. 确定监控内容 在开始编写Python脚本之前,需要确定要监控的内容。比如我们可以监控Linux服务器的 CPU 使用率、内存使用率、磁盘占用情况、网络连接数等等。这里以 CPU 使用率为例。 2. 安装Python 在开始编写Python脚本之前,需要确保服务器中拥有Py…

    Java 2023年5月20日
    00
  • Servlet中文乱码问题解决方案解析

    下面是Servlet中文乱码问题解决方案的详细攻略。 问题描述 在Servlet程序中,当表单提交包含中文字符时,会出现中文乱码的现象。比如表单中提交的文字为“中国”,但在Servlet程序中获取到的却是“中国”。 解决方案分析 原因分析 中文乱码的原因在于,不同的系统、不同的编程语言对中文字符的存储方式不同。当一个字符被从一个系统传递到另一个系统时…

    Java 2023年5月20日
    00
  • springboot 配置DRUID数据源的方法实例分析

    SpringBoot配置Druid数据源的方法实例分析 在SpringBoot中,我们可以使用Druid数据源连接数据库,本文将详细讲解如何在SpringBoot中配置Druid数据源的方法。 引入Druid依赖 在pom.xml文件中,添加Druid依赖: <dependency> <groupId>com.alibaba</…

    Java 2023年5月20日
    00
  • Java经典面试题最全汇总208道(三)

    针对“Java经典面试题最全汇总208道(三)”的攻略,我将会进行详细的讲解,包括其中每个问题的答案和解释。 标题 Java经典面试题最全汇总208道(三) 代码块 下面是一道比较常见的Java面试题: public class Test{ public static void main(String[] args) { String str1 = new …

    Java 2023年5月23日
    00
  • 大家在抢红包,程序员在研究红包算法

    让我来详细讲解一下「大家在抢红包,程序员在研究红包算法」这一话题。 首先,我们需要了解什么是「红包算法」。简单来说,红包算法就是计算如何分配一定数量的金额到多个红包里面,让每个红包的金额尽可能地公平分配。红包算法有很多种,比如「平均法」、「随机法」、「二倍均值法」等等。 接下来,我们先介绍一下「平均法」,因为这是最简单的红包算法之一。平均法的算法逻辑非常简单…

    Java 2023年5月26日
    00
  • 浅析*NIX机器的入侵

    浅析*NIX机器的入侵 本文将从以下几个方面讲解*NIX机器的入侵: 前期准备:探测目标机器 信息采集:获取目标机器的信息 渗透攻击:通过漏洞攻击进入目标机器 后期控制:持久化控制目标机器 前期准备 探测目标机器的IP和操作系统是入侵的第一步,一般可以通过以下几种方式进行探测: 使用扫描器扫描目标机器的IP端口信息,例如使用nmap命令:nmap -sS -…

    Java 2023年6月16日
    00
  • spring注解 @PropertySource配置数据源全流程

    Spring注解 @PropertySource 用于加载指定的属性源,是Spring Framework 4.0版本之后提供的新特性。它允许我们从外部文件或环境变量中读取配置信息,灵活地管理我们的应用程序的数据源。 下面是使用 @PropertySource 配置数据源的完整流程: 引入依赖 在项目的 pom.xml 文件中添加以下依赖: <depe…

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