Java 入门指南:JVM(Java虚拟机)—— Java 类加载器详解

类加载器

类加载器(Class Loader)是 Java 虚拟机(JVM)的一部分,它的作用是将类的字节码文件(.class 文件)从磁盘或其他来源加载到 JVM 中。类加载器负责查找和加载类的字节码文件,并将其转化为 Class 对象。

类加载器从 JDK 1.0 就出现了,最初只是为了满足 Java Applet(已经被淘汰) 的需要。后来,慢慢成为 Java 程序中的一个重要组成部分,赋予了 Java 类可以被动态加载到 JVM 中并执行的能力。

根据官方 API 文档的介绍:

类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

每个 Java 类都有一个引用指向加载它的 ClassLoader。但数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

  • 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。

  • 每个 Java 类都有一个引用指向加载它的 ClassLoader

  • 数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。

组成部分

在 Java 中,类加载器主要有三个层次:

  1. 启动类加载器(Bootstrap ClassLoader):这是最基础的类加载器,由 C++ 实现,通常表示为 null,并且没有父级,负责加载扩展目录下的 jar 包和系统类路径下的核心库( %JAVA_HOME%/lib 目录下的 rt.jarresources.jarcharsets.jar 等 jar 包和类)以及被 -Xbootclasspath 参数指定的路径下的所有类。

    rt.jar:rt 代表“RunTime”,rt.jar 是 Java 基础类库,包含 Java doc 里面看到的所有的类的类文件。也就是说,我们常用内置库 java.xxx.* 都在里面,比如 java.util.*java.io.*java.nio.*java.lang.*java.sql.*java.math.*

  2. 扩展类加载器(Extension ClassLoader):由 Java 实现,负责加载 Java 默认扩展目录下的 jar 包(%JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类)。

  3. 系统类加载器(System/App ClassLoader):也称为应用程序类加载器,由 Java 实现,负责加载用户类路径(classpath)下的所有 jar 包和类。

![[Pasted image 20240915225845.png]]

除了这三个内置的类加载器外,还可以自定义类加载器,通过继承 java.lang.ClassLoader 类的方式实现,以满足特殊的需求。例如,可以通过自定义类加载器来加载网络上的类,或者从数据库中加载类。

对于任意一个类,都需要由它的类加载器和这个类本身一同确定其在 JVM 中的唯一性。也就是说,如果两个类的加载器不同,即使两个类来源于同一个字节码文件,那这两个类就必定不相等(比如两个类的 Class 对象不 equals)。

ClassLoader

除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自 ClassLoader 抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类。

每个 ClassLoader 可以通过 getParent() 获取其父 ClassLoader,如果获取到 ClassLoadernull 的话,那么该类是通过 BootstrapClassLoader 加载的。由于 BootstrapClassLoader 由 C++ 实现,由于这个 C++ 实现的类加载器在 Java 中是没有与之对应的类的,所以拿到的结果是 null。

public abstract class ClassLoader {...// 父加载器private final ClassLoader parent;@CallerSensitivepublic final ClassLoader getParent() {//...}...
}

下面是一个获取 ClassLoader 的示例:

public class PrintClassLoaderTree {public static void main(String[] args) {ClassLoader classLoader = PrintClassLoaderTree.class.getClassLoader();StringBuilder split = new StringBuilder("|--");boolean needContinue = true;while (needContinue){System.out.println(split.toString() + classLoader);if(classLoader == null){needContinue = false;}else{classLoader = classLoader.getParent();split.insert(0, "\t");}}}}

输出结果:

|--sun.misc.Launcher$AppClassLoader@18b4aac2|--sun.misc.Launcher$ExtClassLoader@53bd815b|--null

可以看出:

  • 自定义编写的 Java 类 PrintClassLoaderTreeClassLoaderAppClassLoader
  • AppClassLoader 的父 ClassLoaderExtClassLoader
  • ExtClassLoader 的父 ClassLoaderBootstrap ClassLoader,因此输出结果为 null。

自定义类加载器

除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader抽象类。

ClassLoader 类有两个关键的方法:

  • protected Class loadClass(String name, boolean resolve):加载指定二进制名称的类,实现了双亲委派机制。name 为类的二进制名称,resolve 如果为 true,在加载时调用 resolveClass(Class<?> c) 方法解析该类。

  • protected Class findClass(String name):根据类的二进制名称来查找类,默认实现是空方法。

官方 API 文档中写到:

建议 ClassLoader的子类重写 findClass(String name)方法而不是loadClass(String name, boolean resolve) 方法。

如果我们不想打破双亲委派模型,就需要重写 ClassLoader 类中的 findClass() 方法,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

实现自定义类加载器

以下是我们自行实现自定义类加载器的一个示例:

import java.io.*;public class CustomClassLoader extends ClassLoader {private String pathToBin;public CustomClassLoader(String pathToBin) {this.pathToBin = pathToBin;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] classData = loadClassData(name);return defineClass(name, classData, 0, classData.length);} catch (IOException e) {throw new ClassNotFoundException("Class " + name + " not found", e);}}private byte[] loadClassData(String name) throws IOException {String file = pathToBin + name.replace('.', File.separatorChar) + ".class";InputStream is = new FileInputStream(file);ByteArrayOutputStream byteSt = new ByteArrayOutputStream();int len = 0;while ((len = is.read()) != -1) {byteSt.write(len);}return byteSt.toByteArray();}
}

示例说明:

  • 构造器:接受一个字符串参数,这个字符串指定了类文件的存放路径。
  • 覆写 findClass 方法:当父类加载器无法加载类时,findClass 方法会被调用。在这个方法中,首先使用 loadClassData 方法读取类文件的字节码,然后调用 defineClass 方法来将这些字节码转换为 Class 对象。
  • loadClassData 方法:读取指定路径下的类文件内容,并将内容作为字节数组返回。

类加载器加载规则

JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。

对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。

public abstract class ClassLoader {...private final ClassLoader parent;// 由这个类加载器加载的类。private final Vector<Class<?>> classes = new Vector<>();// 由 JVM 调用,用此类加载器记录每个已加载类。void addClass(Class<?> c) {classes.addElement(c);}...
}

类加载器工作过程

类加载器(Class Loader)在 Java 虚拟机(JVM)中的工作过程是一个复杂而精细的流程。类加载器不仅负责加载类的字节码文件,还要确保类的正确性和初始化。

JVM(Java虚拟机)——类的生命周期与加载过程

类加载器的工作过程可以分为以下几个主要阶段:

  1. 加载(Loading):在加载阶段,类加载器负责读取类的二进制数据,并将其转化为 Class 对象。这一阶段包括以下几个步骤:
  • 查找或获取类的二进制数据:类加载器会根据类的全限定名(例如 com.example.MyClass)查找并加载类的字节码文件。
  • 生成 Class 对象:类加载器将字节码文件转化为 Class 对象,并存放在方法区中。
  1. 验证(Verification):验证阶段是为了确保类文件的字节码符合 Java 虚拟机的规范,防止恶意代码危害虚拟机。验证阶段主要包括以下几个子阶段:

    • 文件格式验证:确保字节流的格式符合 Class 文件格式规范。
    • 元数据验证:确保类的元数据信息(如常量池中的常量)正确无误。
    • 字节码验证:确保字节码指令符合 JVM 规范,不会导致非法操作。
    • 符号引用验证:确保符号引用能正确解析到实际存在的类、接口、方法或字段。
  2. 准备(Preparation):准备阶段主要是为类变量分配内存空间,并设置类变量的初始值。注意,这里的类变量指的是被 static 修饰的变量。实例变量则是在对象实例化时分配内存空间。

  3. 解析(Resolution):解析阶段是将符号引用转换为直接引用的过程。符号引用指的是类名、接口名、方法名等字符串形式的引用,而直接引用则指向目标对象在内存中的地址。

  4. 初始化(Initialization):初始化阶段是执行类构造器 (<clinit>) 方法的过程。在这个阶段,类中的静态变量会被赋予初始值,并执行静态块中的代码。初始化阶段还包括类中非静态方法的调用和类的实例化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/429655.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python|OpenCV-实现识别目标图像中的圆圈(20)

前言 本文是该专栏的第22篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在处理图像检测项目的时候,可能会遇到需要检测目标图像中的“圆圈”需求。笔者在这里举个例子,如下图所示: 在图中有一个篮球,但是我们要找的目标对象并不是篮球,而是篮球它本身的这个…

MySQL安装教程

MySQL安装教程 如果需要删除原有mysql&#xff0c;然后安装过新的&#xff0c;可以参照如何彻底卸载旧mysql重装测试 1. 准备资源 mysql官网直达&#xff1a;https://dev.mysql.com/downloads/mysql/ CADN&#xff1a;https://download.csdn.net/download/luocong321/89592962 …

《深度学习》PyTorch框架 优化器、激活函数讲解

目录 一、深度学习核心框架的选择 1、TensorFlow 1&#xff09;概念 2&#xff09;优缺点 2、PyTorch 1&#xff09;概念 2&#xff09;优缺点 3、Keras 1&#xff09;概念 2&#xff09;优缺点 4、Caffe 1&#xff09;概念 2&#xff09;优缺点 二、pytorch安装 1、安装 2、…

【人工智能】在大型活动中的应用案例

人工智能在娱乐大型活动中的应用 ## 作者主页: 知孤云出岫 目录 **人工智能在娱乐大型活动中的应用****1. 引言****2. 智能票务与入场管理****2.1 动态定价与票务预测****2.2 生物识别技术快速入场****2.3 区块链技术防伪票务管理** **3. 智能观众互动与个性化体验****3.1 个性…

CCF csp认证 小白必看

c支持到C17(还是更高?)&#xff1b;所以学一些封装好的函数功能是必要的---比如STL里的函数&#xff1b; 因为可携带纸质资料&#xff0c;建议打印带入&#xff0c;需要时可翻阅。 【题目概述:】 0-devc环境配置 配置好你常用的编译版本&#xff1a; 想要调试记得开启下选…

Python练习宝典:Day 1 - 选择题 - 基础知识

目录 一、踏上Python之旅二、Python语言基础三、流程控制语句四、序列的应用 一、踏上Python之旅 1.想要输出 I Love Python,应该使用()函数。 A.printf() B.print() C.println() D.Print()2.Python安装成功的标志是在控制台(终端)输入python/python3后,命令提示符变为: A.&…

Linux自主学习篇

用户及权限管理 sudo 是 "superuser do" 的缩写&#xff0c;是一个在类 Unix 操作系统&#xff08;如 Linux 和 macOS&#xff09;中使用的命令。它允许普通用户以超级用户&#xff08;root 用户&#xff09;的身份执行命令&#xff0c;从而获得更高的权限。 useradd…

数据飞轮:打造业务增长的持续循环

在当今数据驱动的世界中&#xff0c;企业必须利用数据的力量才能保持竞争力。然而&#xff0c;仅仅收集和分析数据是不够的&#xff1b;企业必须能够从他们的数据中创造一个持续增长的循环&#xff0c;才能保持成功。其中一种方法就是创建数据飞轮。接下来让我们来探讨一下什么…

网络高级day03(Http)

目录 【1】HTTP简介 【2】 HTTP特点 【3】 HTTP协议格式 1》客户端请求消息格式 1> 请求行 2> 请求头 3> 空行 4> 请求数据 2》服务器响应消息格式 【1】HTTP简介 HTTP协议是Hyper Text Transfer Protocol &#xff08;超文本传输协议&#xff09;的缩写&a…

Python_控制循环语句

if语句单分支结构的语法形式如下&#xff1a; 【操作】输入一个数字&#xff0c;小于10&#xff0c;则打印这个数字(if_test01.py)&#xff1a; num input("输入一个数字&#xff1a;") if int(num)<10: print("小于10的数&#xff1a;"num)条件表达式…

Shader 中的光源

1、Shader 开发中常用的光源属性 Unity当中一共支持四种光源类型&#xff1a; 平行光&#xff08;Directional&#xff09;点光源&#xff08;Point&#xff09;聚光灯&#xff08;Spot&#xff09;面光源&#xff08;Area&#xff09;— 面光源仅在烘焙时有用 不管光源类型到…

[网络层]-IP协议相关特性

IP协议 基本概念 主机 : 配有IP地址,但是不进行路由控制的设备路由器 : 既配有IP地址,又能进行路由控制节点: 主机和路由器的统称 协议头格式 4位版本(version):占四位,用于指定IP协议的版本,例如,使用IPv4,该字段就为44位首部长度: 表示IP协议首部的长度,以32位bit (4字节)…

Linux:终端(terminal)与终端管理器(agetty)

终端的设备文件 打开/dev目录可以发现其中有许多字符设备文件&#xff0c;例如对于我的RedHat操作系统&#xff0c;拥有tty0到tty59&#xff0c;它们是操作系统提供的终端设备。对于tty1-tty12使用ctrlaltF*可以进行快捷切换&#xff0c;下面的命令可以进行通用切换。 sudo ch…

【小bug】使用 RestTemplate 工具从 JSON 数据反序列化为 Java 对象时报类型转换异常

起因&#xff1a;今天编写一个请求时需要通过RestTemplate调用外部接口&#xff0c;获取一些信息&#xff0c;但是在获取了外部接口响应内容后&#xff0c;使用强制转换发现报了类型转换异常。之前也遇到过&#xff0c;但是没记录下来&#xff0c;今天又查了一遍……干脆记录一…

Springboot使用ThreadPoolTaskScheduler轻量级多线程定时任务框架

简介&#xff1a; Spring注解定时任务使用不是很灵活&#xff0c;如果想要灵活的配置定时任务&#xff0c;可以使用xxl-job 或者 quartz等定时任务框架&#xff0c;但是过于繁琐&#xff0c;可能成本较大。所以可以使用ThreadPoolTaskScheduler来灵活处理定时任务 ThreadPoolT…

【C++】二叉搜索树的底层以及实现

个人主页 文章目录 ⭐一、二叉搜索树的概念&#x1f680;二、二叉搜索树性能分析&#x1f3dd;️三、二叉搜索树的操作1. 插入2. 查找3. 删除4. 遍历节点 &#x1f384;四、二叉搜索树的实现&#xff08;K模型&#xff09;&#x1f389;五、二叉搜索树的应用1. K模型2. KV模型…

基于ACMEv2协议的免费SSL证书申请-支持Let‘s Encrypt/Google/ZeroSSL

项目&#xff1a;https://github.com/cook-code-jazor/acmex 非开源&#xff0c;使用webui管理证书的申请&#xff0c;所有文件本地化存储&#xff0c;支持windows/linux/osx。 证书申请直连ACMEv2服务商&#xff0c;没有任何中间接口&#xff0c;支持Lets Encrypt/Google/Ze…

图说GPT网络结构(参数量与计算量估计)

现在AI领域的主流模型几乎都是Transformer网络架构衍生而来。大热的LLM中的生成类模型很多都是来自于Transformer的变体&#xff0c;即decoder only架构。而GPT就是该类中的经典模型。尽管现在变体甚多&#xff0c;但大多没有根本性地改变其套路。 为了阐述方便&#xff0c;首…

云平台在大规模设备管理和数据分析中的作用

在当代数字化转型的浪潮中&#xff0c;云平台作为信息技术基础设施的核心组件&#xff0c;扮演着无可替代的角色&#xff0c;尤其在大规模设备管理和数据分析领域&#xff0c;其重要性和影响力日益凸显。本文旨在深入探讨云平台如何通过其独特的优势&#xff0c;促进数据的高效…

微软AI核电计划

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…