Java 类加载过程与类加载器详细介绍

让我为您讲解一下 "Java 类加载过程与类加载器详细介绍" 的完整攻略。

什么是类加载?

Java 语言是一种面向对象程序设计语言,其中最基本的组成单位是类。在 Java 语言中,类是由编译器编译 Java 代码后生成的字节码文件,这些字节码文件最终是由 Java 虚拟机来执行的。而在 Java 虚拟机的执行过程中,类加载器则负责将类文件加载到 JVM 中进行执行。

类加载过程是 Java 虚拟机执行程序时不可避免的一部分。类加载器负责管理 Java 类的加载、验证、解析和初始化等工作。只有在类被加载到 JVM 中后,才能开始执行相应的方法。

类加载的过程

类加载过程主要包括三个步骤:加载、链接和初始化。

加载

类加载器在类加载的过程中,首先需要从 classpath 中找到指定的字节码文件,并将其加载到 JVM 的内存中。在运行期间,类加载器将一个类的全限定名作为输入,然后返回表示此类的实例。类加载器并不关心这些字节码的来源,可以来自本地磁盘、网络等任何地方。

一个类文件只有在第一次加载时才会被解析并填充到内存中去

链接

在将 Class 文件加载到内存之后,Java 虚拟机还需要完成以下三个步骤的检查和处理:

  1. 验证:主要是对字节码文件进行验证,确保其格式符合 JVM 的要求,比如能否转换成正确的 Class 文件格式,是否有明显的安全问题等;
  2. 准备:这个阶段主要是为类变量(即 static 变量)分配内存并设置其初始值;
  3. 解析:将符号引用转换为直接引用,这是Java虚拟机规范预留的,目的在于支持使用 Java 语言实现的“动态绑定”。

初始化

在完成类的加载和链接之后,JVM 执行类的初始化操作。初始化过程是类加载过程中的最后一个阶段,该阶段目的是对类变量进行赋值,静态语句块和静态方法也会得以执行。类中的类变量是在准备阶段分配的内存空间,而在初始化阶段会完成变量的赋值,因此才能被使用。一个类在进行初始化时,可能需要执行与其他类有依赖关系的变量或方法。JVM 通常采用按照代码中顺序初始化的方式完成初始化工作。

类加载器分类

Java 类加载器一般被分为以下四种:

  1. BootStrapClassLoader(启动类加载器):用于加载 JDK 中的核心类库,是 Java 虚拟机自带的类加载器,负责加载 Java 的核心类库,如 JRE/lib 目录下的 rt.jar 包中所有的类。
  2. ExtensionClassLoader(扩展类加载器):用于加载在 Java 虚拟机设置的扩展目录(JAVA_HOME/jre/lib/ext 或者由系统变量 java.ext.dir 指定的目录)中的所有类。它的父类加载器为启动类加载器。
  3. AppClassLoader(系统类加载器):负责加载应用程序 ClassPath 目录下的所有类。当执行 java 命令时,系统类加载器会自动从环境变量 CLASSPATH 中获取类加载路径。
  4. 自定义类加载器:开发人员可以根据需要自己编写类加载器,继承自ClassLoader,实现findClass()方法,通过定义相应的规则,例如从网络上下载类文件,从特定文件夹中读取类文件等。

类加载器示例

下面,我用两个示例来说明类加载器的应用。

示例一

假设我们有以下两个 Java 类,它们位于 /path/to/ClassA 和 /path/to/ClassB 中,ClassB 文件需要依赖 ClassA。

package org.example;

public class ClassA {
    public void hello() {
        System.out.println("Hello from ClassA!");
    }
}
package org.example;

public class ClassB {
    public void hello() {
        ClassA a = new ClassA();
        a.hello();
        System.out.println("Hello from ClassB!");
    }
}

在我们使用 javac 命令编译这两个类之后,会在我们当前的工作目录下生成两个 .class 文件,在这里使用相对路径表示。

接下来,我们在一个 Java 类中,使用自定义类加载器加载这两个类,并执行 ClassB 类的 hello 方法。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CustomClassLoader extends ClassLoader {
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] bytes = Files.readAllBytes(Paths.get(name + ".class"));
            return defineClass(name, bytes, 0, bytes.length);
        } catch (Exception e) {
            return super.findClass(name);
        }
    }

    public static void main(String[] args) throws ClassNotFoundException,
            IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        CustomClassLoader customClassLoader = new CustomClassLoader();
        Class<?> classA = customClassLoader.loadClass("org.example.ClassA");
        Object instanceA = classA.newInstance();
        Method methodA = classA.getMethod("hello");
        methodA.invoke(instanceA);

        Class<?> classB = customClassLoader.loadClass("org.example.ClassB");
        Object instanceB = classB.newInstance();
        Method methodB = classB.getMethod("hello");
        methodB.invoke(instanceB);
    }
}

通过运行上述代码,我们可以看到,类 ClassB 被成功加载并调用。

示例二

再来看一个示例,它用这自定义类加载器,从指定的远程 HTTP 服务器中加载类文件。

我们使用 Maven 的一个插件(远程 JAR 插件)来实现将 Demo 类加载到服务器上的过程。在这个示例中,我们假设这个插件将 Demo 类和它所依赖的类上传到了服务器的指定目录下。

import java.net.URL;
import java.net.URLClassLoader;

public class CustomClassLoaderDemo {
    public static void main(String[] args) throws Exception {
        URL[] urls = {new URL("http://localhost:/path/to/Demo.jar")};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class<?> clazz = classLoader.loadClass("org.example.Demo");
        Object obj = clazz.newInstance();
        System.out.println(obj.toString());
    }
}

在这个示例中,我们使用了 URLClassLoader 类来加载 Demo 类。它的作用是创建一个新的 URLClassLoader 对象,然后从指定的远程服务器中加载 Demo 类。需要注意的是,在使用URLClassLoader类时一定要提供正确的 URL 连接,否则会抛出 MalformedURLException 异常。

以上就是 Java 类加载过程与类加载器详细介绍的完整攻略,希望能够帮助到您。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java 类加载过程与类加载器详细介绍 - Python技术站

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

相关文章

  • jmeter同步定时器

    JMeter同步定时器 简介 JMeter是一个功能强大的性能测试工具。其中一个重要组件是定时器(Timer),它能够在脚本执行过程中插入一定的延时,来模拟真实场景下的用户行为。而JMeter同步定时器(Synchronizing Timer)则是一个特殊的定时器,它能够实现多个线程之间的同步,以确保它们在相同的时间开始执行。 使用场景 在一些场景下,多个用…

    其他 2023年3月28日
    00
  • Kotlin Flow操作符及基本使用详解

    Kotlin Flow操作符及基本使用详解 什么是Kotlin Flow Kotlin Flow是基于协程提供的一种异步数据流实现,可以帮助我们实现类似ReactiveX中的数据流的功能,但是更加轻量级和易于使用,适合于在Kotlin代码中使用。Kotlin Flow可以将数据流的操作分发到协程上,同时可以避免回调地狱的问题,让代码更加简洁。 Flow的基本…

    other 2023年6月27日
    00
  • DOS命令行下常见的错误信息

    以下是关于DOS命令行下常见的错误信息的完整攻略。 什么是DOS命令行下的错误信息 DOS命令行是一个命令行操作系统,在执行指令时,如果遇到错误,系统会返回相应的错误信息。这些错误信息可以帮助用户理解问题的来源,以便更好地解决问题。 常见的DOS错误信息 错误码和说明 下面是一些常见的DOS错误信息,以及它们的含义: The system cannot fi…

    other 2023年6月26日
    00
  • securecrt破解安装详细教程

    SecureCRT破解安装详细教程 SecureCRT是一款非常流行的终端仿真软件,但是官方版本需要付费才能使用,本文将介绍如何破解SecureCRT并进行安装,以实现免费使用。 步骤1:下载破解文件 首先,需要下载SecureCRT的破解文件,可以在网络上搜索到。 步骤2:停止官方版SecureCRT进程 在进行破解之前,需要先停止正常运行的SecureC…

    其他 2023年3月28日
    00
  • C语言中网络地址与二进制数之间转换的函数小结

    下面是本人对于“C语言中网络地址与二进制数之间转换的函数小结”的攻略: 网络地址与二进制数的转换 在进行网络编程时,经常需要将IP地址和端口号表示成二进制数(例如:IPv4地址是32位的二进制数),也需要将二进制数转换成IP地址和端口号表示。 这里推荐C语言提供的一些库函数以及方法。 函数1:inet_pton 函数inet_pton可以将一个字符串形式的I…

    other 2023年6月26日
    00
  • SpringBoot结合mybatis-plus实现分页的项目实践

    SpringBoot结合mybatis-plus实现分页的项目实践 本攻略将详细讲解如何在SpringBoot项目中结合mybatis-plus实现分页功能。以下是完整攻略: 步骤一:添加依赖 首先,在项目的pom.xml文件中添加mybatis-plus的依赖。可以通过Maven或Gradle进行添加。 示例说明1:使用Maven添加mybatis-plu…

    other 2023年10月18日
    00
  • Centos8无法安装docker问题的解决方法

    以下是详细的CentOS 8安装Docker的攻略,包括两条示例说明: 问题描述 在CentOS 8中尝试安装Docker,可能会出现以下错误信息: Last metadata expiration check: 0:01:00 ago on Wed 05 May 2021 09:10:54 AM CST. No match for argument: do…

    other 2023年6月27日
    00
  • 一篇文章带你了解C++Primer学习日记–处理数据

    很抱歉,我无法提供关于特定文章的攻略,因为我无法直接访问或引用特定的文章内容。然而,我可以为您提供有关处理数据的C++ Primer学习日记的一般攻略。 1. 数据类型和变量声明 在C++中,首先需要了解不同的数据类型,如整数、浮点数、字符等。可以使用关键字int、float、char等来声明变量,并为其分配内存空间。 int age = 25; float…

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