Java类加载

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Java类加载

类加载

类加载过程

类加载过程Java类加载过程大致分为加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)几个阶段。

  1. 加载(Loading)
  • 这是类加载的第一阶段,类加载器负责从文件系统、网络或其他来源读取类的字节码数据,并根据这些数据在JVM内部创建一个Class对象。
  1. 链接(Linking): 链接阶段又分为三个子阶段:
  • 验证(Verification):确保被加载类的正确性,验证字节码确保它遵循Java语言的规范,没有安全问题。
  • 准备(Preparation):为类变量(static字段)分配内存,并设置类变量的默认初始值。
  • 解析(Resolution):将类、接口、字段和方法的符号引用转换为直接引用。
  1. 初始化(Initialization)
  • 初始化阶段是执行类构造器<clinit>()方法的过程。这个方法由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生。当初始化一个类时,如果其父类还没有被初始化,则先触发其父类的初始化。
解析阶段

解析阶段:在解析阶段,虚拟机会对类加载阶段得到的二进制数据中的符号引用进行处理。符号引用是一组符号来描述所引用的目标,可以看作是一种抽象的概念,并不直接指向目标的内存地址。例如,一个符号引用可能是一个类的全限定名(Fully Qualified Name)。

类常量池(Class Constant Pool)

类常量池是Class文件结构的一部分,主要存储两种类型的常量:

  1. 字面量:这包括了文本字符串、声明为final的基本类型值(如int、long等)和引用类型值。
  2. 符号引用:这包括了类和接口的全限定名、字段的名称和描述符、方法的名称和描述符以及方法接口的名称和类型描述符等。

转换为直接引用:解析阶段的主要任务是将这些符号引用转换为直接引用。这意味着符号引用会被转换成具体的内存地址或是指向内存中的指针,这些引用直接指向目标的存储位置。例如,类的符号引用将被转换成指向方法区中类数据的指针。

不同类型的符号引用:在Java中,符号引用包括类和接口的名称、字段的名称和描述符、方法的名称和描述符等。这些引用在解析阶段被转换为可以直接定位到目标的引用。

动态链接:Java支持动态链接,即一些符号引用在类加载时不会立即解析。例如,对于方法的引用,可能会在第一次使用时才进行解析。这种机制是Java动态绑定和晚期绑定特性的基础。

这个过程是由JVM在运行时自动管理的,确保Java程序在不同环境下具有良好的可移植性和灵活性。解析阶段的处理使得Java能够在运行时动态加载和链接类,这是实现多态和动态绑定的关键

[!tip]

静态对象引用存在方法区中,但是对象实例还是存在堆中

类加载器

java平台提供了三种内建的类加载器,它们按照父子关系层次结构工作:

  1. 引导类加载器(Bootstrap ClassLoader)
  • 它是虚拟机的一部分,用C++编写。负责加载$JAVA_HOME/jre/lib目录下的,或者由-Xbootclasspath参数指定路径中的核心Java库(如java.lang.*)。
  1. 扩展类加载器(Extension ClassLoader)
  • 它负责加载$JAVA_HOME/jre/lib/ext目录下的,或者由系统属性java.ext.dirs指定路径中的Java扩展库。它是Java编写,由引导类加载器加载。
  1. 系统(应用)类加载器(System/Application ClassLoader)
  • 它负责加载环境变量CLASSPATH或者系统属性java.class.path指定路径中的类库。它是程序中默认的类加载器,一般来说,我们自定义的类都是由它来加载的。

双亲委派模型

Java类加载器采用的是一种称为“双亲委派模型”的策略。当一个类加载器收到类加载请求时,它首先不会尝试自己去加载这个类,而是把这个请求委托给父类加载器去完成。只有当父类加载器反馈自己无法完成这个加载请求(它在搜索范围内没有找到对应的类)时,子类加载器才会尝试自己去加载这个类。这个模型的优点包括:

  • 防止类的重复加载。
  • 保护程序安全,防止核心API被随意篡改。

自定义类加载器通常是通过继承java.lang.ClassLoader类并重写findClass方法来实现的,以支持从非标准来源加载类,如从加密文件、网络、动态生成的字节码等。

为什么打破双亲委派机制

1. 热部署

在一些企业应用中,需要对应用或者模块进行热替换,如果遵循双亲委派机制,如果被父类加载器加载的话就不能呗替换和更新,因为副贼加载器运行期间不会卸载已经加载的类,通过打破双亲委派机制,使用自定义类的加载策略,可以运行时替换或者更新类

2. 不同版本的类共存

某些情况下不同的场景加载类的不同版本,

3. 自定义类加载逻辑

某些高度定义的场景比如从特定的网络位置加载类,或者从加密文件中加载类

4. 插件化架构

在插件化或模块化架构的应用中,每个插件或模块可能需要使用完全独立的类加载器,以确保模块间的完全隔离。这样,每个模块就可以加载和卸载而不影响其他模块,同时也允许模块使用不同版

怎么自定义类加载器

继承java.lang.ClassLoader类,并重写其findClass方法。

  1. 继承ClassLoader:创建一个新类,继承自java.lang.ClassLoader
  2. 重写findClass方法:在这个方法中,你需要定义如何查找类。如果类在你的搜索范围内(比如,你的文件系统中的一个特定目录),你需要读取类的字节码,然后调用defineClass方法来定义这个类。
  3. (可选)重写loadClass方法:在某些复杂的场景下,如果你想完全控制类的加载过程,可以重写loadClass方法。但是这样做时需要非常小心,以避免破坏双亲委派模型。

从指定的文件路径加载类:

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class CustomClassLoader extends ClassLoader {
    private String pathToBin;
    public CustomClassLoader(String pathToBin) {
        this.pathToBin = pathToBin;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classBytes;
        try {
            classBytes = loadClassFromFile(name);
        } catch (IOException e) {
            throw new ClassNotFoundException("Could not load class " + name, e);
        }
        return defineClass(name, classBytes, 0, classBytes.length);
    }
    private byte[] loadClassFromFile(String fileName) throws IOException {
        File file = new File(pathToBin + fileName.replace('.', File.separatorChar) + ".class");
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int len;
        while ((len = fis.read()) != -1) {
            bos.write(len);
        }
        fis.close();
        return bos.toByteArray();
    }
}

使用自定义类加载器:

public class Main {
    public static void main(String[] args) throws Exception {
        String classPath = "/path/to/your/classes";
        CustomClassLoader classLoader = new CustomClassLoader(classPath);
        Class<?> clazz = classLoader.loadClass("com.example.YourClass");
        Object obj = clazz.newInstance();
        System.out.println("Class loaded and instance created: " + obj.getClass().getName());
    }
}

CustomClassLoader会从给定的文件路径加载类。你需要将/path/to/your/classes替换为实际存放.class文件的路径。注意,类的完整名称(包括包名)需要正确地传递给loadClass方法。

相关文章
|
6月前
|
前端开发 安全 Java
聊聊Java虚拟机(一)—— 类加载子系统
虚拟机就是一款用来执行虚拟计算机指令的计算机软件。它相当于一台虚拟计算机。大体上,虚拟机分为系统虚拟机和程序虚拟机。系统虚拟机就相当于一台物理电脑,里面可以安装操作系统;程序虚拟机是为了执行单个计算机程序而设计出来的虚拟机。其中 Java 虚拟机就是**执行 Java 字节码指令的虚拟机**。
70 2
|
Java Go Nacos
解决Spring Boot与Nacos集成时的类加载问题: java.lang.NoClassDefFoundError: org/springframework/boot/context/prope
解决Spring Boot与Nacos集成时的类加载问题: java.lang.NoClassDefFoundError: org/springframework/boot/context/prope
246 1
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
112 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
3月前
|
设计模式 存储 安全
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
18 Java反射reflect(类加载+获取类对象+通用操作+设计模式+枚举+注解)
96 0
|
4月前
|
存储 前端开发 Java
(二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器
上篇《初识Java虚拟机》文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再交由执行引擎执行。本文中则会对Java虚拟机的类加载机制以及执行引擎进行全面分析。
|
4月前
|
Java
java通过idea启动查看类加载来源信息
java通过idea启动查看类加载来源信息
141 0
|
4月前
|
存储 算法 Java
JAVA程序运行问题之Java类加载到JVM中加载类时,实际上加载的是什么如何解决
JAVA程序运行问题之Java类加载到JVM中加载类时,实际上加载的是什么如何解决
|
4月前
|
Java
Java中的动态类加载详解
Java中的动态类加载详解
|
5月前
|
安全 前端开发 Java
java类加载以及双亲委派机制
web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已,经是司空见惯的事情,web容器要支持jsp的修改后不用重启。
43 0
|
5月前
|
安全 Java 定位技术
Java类加载大冒险,谁能将它变成漫画
Java类加载大冒险,谁能将它变成漫画
26 0
下一篇
无影云桌面