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日

相关文章

  • 安卓 获取手机IP地址的实现代码

    获取安卓手机的IP地址可以通过以下步骤实现: 添加网络权限:在AndroidManifest.xml文件中添加以下权限: <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" /> <uses-permission androi…

    other 2023年7月31日
    00
  • php.ini 配置文件的深入解析

    那么让我来为您详细介绍“php.ini配置文件的深入解析”的攻略吧。 什么是php.ini配置文件 php.ini文件是PHP的配置文件,它可以对PHP运行的各种参数进行设置和维护。php.ini文件包含了所有PHP的参数配置选项。 一般在安装PHP后,PHP会自动创建php.ini文件,并将其保存在PHP安装目录下的conf.d文件夹中。如果你需要修改ph…

    other 2023年6月25日
    00
  • 微信小程序连接服务器展示MQTT数据信息的实现

    下面是“微信小程序连接服务器展示MQTT数据信息的实现”的完整攻略,具体步骤如下: 准备工作 安装微信开发者工具,并在工具中创建一个小程序项目; 在小程序的“app.json”文件中引入“MQTT”等需要的依赖; 在小程序中引入所需的mqtt.js库,并配置相应的参数:Broker URL、Client ID等; 实现连接服务器 创建连接服务器的函数,例如“…

    other 2023年6月26日
    00
  • Java中超详细this与super的概念和用法

    Java中超详细this与super的概念和用法 1. this关键字 在Java中,this关键字可以代表当前对象的引用。this关键字可以用于以下几种情况: 1.1 调用当前类的构造函数 在一个类中,可以存在多个构造函数,有些构造函数可能需要调用其它构造函数完成一些初始化操作,此时可以使用this关键字来调用当前类中的其它构造函数。 示例代码: publ…

    other 2023年6月27日
    00
  • Java 线程的优先级(setPriority)案例详解

    Java 线程的优先级(setPriority)案例详解 1. 简介 Java多线程中,每个线程都有一个优先级。优先级决定了线程在竞争CPU资源时的相对顺序。线程的优先级范围是1到10,其中1为最低优先级,10为最高优先级。 在Java中,可以使用setPriority方法为线程设置优先级。本文将详细介绍如何使用setPriority方法,并提供两个示例说明…

    other 2023年6月28日
    00
  • (科普)什么是IP地址?它是否会暴露你的个人信息?

    (科普)什么是IP地址?它是否会暴露你的个人信息? 什么是IP地址? IP地址(Internet Protocol Address)是互联网上用于标识和定位设备的一组数字。它是一个由32位或128位二进制数字组成的地址,用于在网络中唯一标识设备。IP地址可以分为IPv4和IPv6两种类型。 IPv4 IPv4是目前广泛使用的IP地址版本。它由四个由点分隔的十…

    other 2023年7月29日
    00
  • C++11特性小结之decltype、类内初始化、列表初始化返回值

    下面我将对“C++11特性小结之decltype、类内初始化、列表初始化返回值”的相关知识点进行详细讲解。 1. decltype decltype 用于查询表达式的类型。一般地说,表达式包括变量,函数调用,类型转换和运算符等等。在使用 decltype 时,编译器并不实际计算表达式的值,而只是分析表达式的类型并将其作为 decltype 的结果返回。 下面…

    other 2023年6月20日
    00
  • Spring Bean的生命周期详细介绍

    Spring Bean的生命周期可分为以下7个阶段: 实例化Bean对象:在Spring IoC容器中,当应用程序需要使用Bean对象时,容器根据配置文件中的Bean定义信息,创建Bean对象。这个过程就是实例化Bean对象。 设置Bean属性(依赖注入):在Bean对象实例化之后,Spring IoC容器会将配置文件中Bean定义的属性值通过Setter方…

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