【Java】一文讲解Java类加载机制

Java 类加载机制是 Java 运行时的核心组成部分,负责在程序运行过程中动态加载和连接类文件,并将其转换为可执行代码。理解类加载机制,能更容易理解你一行行敲下的Java代码是如何在JVM虚拟机上运行起来。并且理解类加载机制之后,我们也能掌握如何自定义类加载器,如何做热更新等。

// 准备好了吗,要开始咯!(下图需要离远点看)
java

一、JVM如何启动

JVM启动

启动过程如下:

  • 配置JVM装载环境
    • 查找JVM.dll文件
    • 装载JVM.dll文件
  • 解析虚拟机参数
    • 参数解析
    • 参数验证
  • 设置线程栈大小
  • 执行main方法(jdk源码中java.c的JavaMain方法)
    • 创建JVM实例
    • 加载主类class(调用jvm的java层代码的loadClass)
    • 查找main方法
    • 执行main方法

二、类加载器

  1. 引导类加载器(Bootstrap ClassLoader)

加载路径:sun.boot.class.path

引导类加载器主要负责加载最最核心的java类型。 这些类库位于jre目录的lib目录下**. 比如:rt.jar, charset.jar等,

引导类加载器是由C++帮我们实现的, 然后c++语言会通过一个Launcher类将扩展类加载器(ExtClassLoader)和应用程序类加载器(AppClassLoader)构造出来, 并且把他们之间的关系构建好.

  1. 扩展类加载器(Ext ClassLoader)

加载路径:java.ext.dirs

扩展类加载器主要是用来加载扩展的jar包。 加载jar的目录位于jre目录的lib/ext扩展目录中的jar包

  1. 应用程序类加载器(App ClassLoader)

加载路径:java.class.path

主要是用来加载用户自己写的类的。 负责加载classPath路径下的类包

  1. 自定义类加载器

负责加载用户自定义路径下的类包

三、类加载过程

类加载过程

  1. 加载(Loading):把class文件加载到内存
  2. 链接(Linking)
    1. 验证(Verification):校验文件是否符合class规范
    2. 准备(Preparation):静态变量赋默认值
    3. 解析(Resolution):把类型方法属性等解析为直接引用
  3. 初始化(Initializing):静态变量赋初始值,调用静态代码块
  4. 使用
  5. 卸载

类加载机制:

  • 全盘委托机制:当ClassLoader加载类时,除非显示指定另一个ClassLoader,否则该类的引用和依赖也由这个ClassLoader载入
  • 双亲委派机制:ClassLoader在加载类时,会首先让父类去加载,只有当父类无法加载的时候,才会由子类来加载

四、双亲委派原则

双亲委派原则是指ClassLoader在类加载时,会自下而上询问父类是否加载,如果没有加载先由父类加载,父类加载不到再由其子类自上而下加载

双亲委派

双亲委派的好处是安全

相关源码:

// ClassLoader.class
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

Class.forName和ClassLoader.loadClass的区别

  1. class.forName()将类的.class文件加载到jvm中后,还会对类进行解释,执行类中的static块。也可通过传参指定是否初始化
  2. loadClass只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块

jvm

五、类加载应用:热更新

1. ClassLoader热更新

自定义ClassLoader的子类(打破双亲委派原则),使用ClassLoader的defineClass即可加载新的byte数组覆盖原有的字节码

  1. 自定义ClassLoader
  2. 读取要热更的class文件并转换成byte数组
  3. 重写findClass方法并调用ClassLoader的defineClass

2. Instrument热更新

Java Instrumentation 是 JDK5 之后提供接口。使用这组接口,我们可以获取到正在运行 JVM 相关信息,使用这些信息我们构建相关监控程序检测 JVM。另外, 最重要我们可以替换修改类的,这样就实现了热更新。

Instrumentation提供premain和agentmain两种方式

1. premain方式

这种方式需要在虚拟机参数指定 Instrumentation 程序。使用方式如下:

java -javaagent:jar Instrumentation_jar -jar xxx.jar

并且在执行java的main方法之前,会先执行在mainfest中指定的premainClass中的类里的premain方法(需要提前定一个用于热更新的类,并加上premain方法)。之后就可以通过Instrumentation接口调用其中的redefineClasses方法来热更新类了

应用示例-热更新实现:

  1. 新建reload工程,定义热更新工具类ClassReloadUtils,并添加premain方法,缓存JVM层传进来的Instrumentation接口的实例
private static Instrumentation inst = null;
private static final Object LOCK = new Object();
private ClassReloadUtils() {
}
/**
 * 此方法由JAVA虚拟机调用
 * 
 * @param agentArgs
 * @param ins
 */
public static void premain(String agentArgs, Instrumentation ins) {
	synchronized (LOCK) {
		if (inst == null) {
			inst = ins;
			StringBuilder builder = new StringBuilder("[");
			builder.append(new Timestamp(System.currentTimeMillis()));
			builder.append("]-");
			builder.append(CLASS_RELOAD_OPEN_TIPS);
			System.out.println(builder.toString());
		}
	}
}
  1. reload的pom文件添加Premain-Class标签指定premain方法所在的类,并指定Can-Redefine-Classes为true
<build>
	<finalName>mmo.reload</finalName>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-jar-plugin</artifactId>
			<version>2.3.1</version>
			<configuration>
				<archive>
					<manifestEntries>
						<Premain-Class>com.xxx.ClassReloadUtils</Premain-Class>
						<Can-Redefine-Classes>true</Can-Redefine-Classes>
					</manifestEntries>
				</archive>
			</configuration>
		</plugin>
    </plugins>
</build>

  1. 用ClassLoader加载热更类,并把要热更的class文件读到byte数组,创建ClassDefinition类
String className = classFile.getName();
className = className.replace(CLASS_EXT, "");
// loadClass
Class<?> clasz = classLoader.loadClass(className);
byte[] bs = FileUtils.toByteArray(classFile);
return new ClassDefinition(clasz, bs);
  1. 使用启动时缓存的Instrumentation接口调用redefineClasses,并传入要热更的ClassDefinition类,完成热更
ClassDefinition[] definitions = classDefinitions.toArray(new ClassDefinition[classDefinitions.size()]);
try {
    inst.redefineClasses(definitions);
} catch (Exception e) {
    return ReloadResult.failed(String.format(CLASS_RELOAD_FAILED, e.getMessage()));
}

2. agentmain方式

arthas使用agentmain加attach方式实现动态监控以及动态修改字节码

不同于premain方式,agentmain允许在JVM启动之后进行代理,它的实现方式和premain类似,先定义一个用于热更新的类,并添加agentmain方法。接着读取外部传入 class 文件,调用 Instrumentation#redefineClasses,这个方法将会使用新 class 替换当前正在运行的 class,这样我们就完成了类的修改。

步骤如下:

  1. 创建热更代理工程,定义热更工具类AgentMain
  2. 类似premain方式,pom文件中添加指定工具类已经定义为可重定义class为true
<!--指定 class 名字-->
<Agent-Class>
    com.andyxh.AgentMain
</Agent-Class>
<Can-Redefine-Classes>
    true
</Can-Redefine-Classes>
  1. 在热更工具类AgentMain实现agentmain方法,在其中调用Instrumentation.redefineClasses完成热更逻辑

至此热更逻辑已经结束,后面则需要利用JVM提供的Attach功能把代理动态加进去

  1. 通过JVM的attach动态添加agent
System.out.println("当前热更新工具 jar 路径为 "+jarPath);
VirtualMachine vm = VirtualMachine.attach(pid);//7997是待绑定的jvm进程的pid号
// 运行最终 AgentMain 中方法
vm.loadAgent(jarPath, classPath);

其中的Attach原理:Attach API 位于 tools.jar 包,可以用来连接目标 JVM。Attach API 非常简单,内部只有两个主要的类,VirtualMachineVirtualMachineDescriptor

VirtualMachine 代表一个 JVM 实例, 使用它提供 attach 方法,我们就可以连接上目标 JVM。

 VirtualMachine vm = VirtualMachine.attach(pid);

VirtualMachineDescriptor 则是一个描述虚拟机的容器类,通过该实例我们可以获取到 JVM PID(进程 ID),该实例主要通过 VirtualMachine#list 方法获取。

for (VirtualMachineDescriptor descriptor : VirtualMachine.list()){
    System.out.println(descriptor.id());
}

java

3. 热更新的局限性

  • premain和agentmain均在类文件加载后,因此不能重新定义一个不存在类
  • 热更的类和旧的类继承的父类必须相同
  • 热更的类和旧的类继承的接口必须相同
  • 热更的类和旧的类的访问修饰符,字段必须相同
  • 热更的类和旧的类新增或删除的方法必须是private static/final修饰
  • 热更的类可以修改方法体

更多技术干货,欢迎关注我

qrcode

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot整合Canal

一 linux docker compose版本 1.第一步&#xff1a;基础环境 &#xff08;1&#xff09;第1步&#xff1a;安装jak、maven、git、nodejs、npm yum install maven mvn -v 安装maven时会帮安装jdkyum install git git --version 2.27.0yum in…

提升客户体验!十大热门客户服务软件解决方案推荐

现代企业深切认识到客户关系对于成功至关重要。如今&#xff0c;顾客越来越偏向于个性化和情境化服务的企业。根据Forrester的研究&#xff0c;将优先考虑建立更好客户关系以实现长期增长将是2023年业务成功的关键。 为了评估和改善客户关系&#xff0c;您需要一个系统化的方式…

DataFunSummit:2023年数据湖架构峰会-核心PPT资料下载

一、峰会简介 现今&#xff0c;很多企业每天都有PB级的数据注入到大数据平台&#xff0c;经过离线或实时的ETL建模后&#xff0c;提供给下游的分析、推荐及预测等场景使用。面对如此大规模的数据&#xff0c;无论是分析型场景、流批一体、增量数仓都得益于湖仓一体等数据湖技术…

数据分析硬核工具Origin各版本安装指南

下载链接 https://pan.baidu.com/s/12mENFtRFdNaLzVKmE6w_Uw?pwd0531 1.鼠标右击【Origin 2022(64bit)】压缩包&#xff08;win11及以上系统需先点击显示更多“选项”&#xff09;选择【解压到 Origin 2022(64bit)】。 2.双击打开解压后的【Origin 2022(64bit)】文件夹。 3.…

【Matlab】CNN卷积神经网络时序预测算法

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/88681558 一&#xff0c;概述 CNN&#xff08;Convolutional Neural Network&#xff0c;卷积神经网络&#xff09;是一种前馈神经网络&#xff0c;主要用于处理具有类似网格结构的数据&#xff0c;例如图像…

2023十大编程语言及未来展望

2023十大编程语言及未来展望 1. 2023年十大编程语言排行榜2. 十大编程语言未来展望PythonCCJavaC#JavaScriptPHPVisual BasicSQLAssembly language 1. 2023年十大编程语言排行榜 TIOBE排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量&#xff0c;并使用搜索引擎&a…

Python中的用户交互函数详解,提升用户体验!

更多Python学习内容&#xff1a;ipengtao.com 用户进行交互的Python应用程序&#xff0c;有许多常用的用户交互函数可以帮助创建更具吸引力和友好的用户界面。本文将介绍一些常用的Python用户交互函数&#xff0c;并提供详细的示例代码&#xff0c;以帮助大家更好地理解它们的用…

数字人私人定制

数字人是什么&#xff1f; 在回答这个问题之前&#xff0c;我们先回答另一个问题&#xff0c;人如何与人工智能交流&#xff1f;目前可以通过文字、语音、电脑屏幕、手机屏幕、平板、虚拟现实设备等和人工智能交流&#xff0c;为了得到更好的交流体验&#xff0c;人工智能必然…

CANopen DS402 Homing方法分析

本文主要分析CANopen DS402中各种Homing方法。 一 装置通用结构 讲解Homing方法前&#xff0c;需要了解一下装置的通用结构&#xff0c; 装置一般左右各有一个limit switch&#xff0c;即限位开关&#xff0c;用来控制移动范围&#xff0c;分别叫negative限位开关和positive…

Flowable-升级为7.0.0.M2-第二节

目录 替换变化的类和配置把javax.servlet 替换为 jakarta.servlet修改redis的配置配置logging.level.org.springframework.boot.autoconfigureerror避免影响视听 替换变化的类和配置 把javax.servlet 替换为 jakarta.servlet import javax.servlet.ServletContext; import ja…

运行时错误‘53’文件未找到:MathPage.WLL,安装MathType后Word不能复制粘贴问题的解决

两步解决&#xff1a; 1. 打开Word-->文件-->选项-->信任中心-->信任中心设置-->受信任位置&#xff0c;解决宏问题 添加如下受信任位置&#xff0c; 我的路径&#xff1a;C:\Program Files\Microsoft Office\root\Office16\STARTUP\ 2. 找到MathType下的MathT…

1panel使用指南(一)面板安装

一、1panel简介 1Panel是杭州飞致云信息科技有限公司推出的产品 [1]&#xff0c;帮助用户实现快速建站。 [2]是一款现代化、开源的Linux服务器运维管理面板&#xff0c;于2023年3月推出&#xff0c;深度集成WordPress和Halo&#xff0c;一键完成域名绑定、SSL证书配置等操作&a…

元旦特辑:Note5---插入排序

目录 前言&#x1faa9; 1. 排序的概念运用&#x1f7e3; 1.1 排序的概念&#x1f7ea; 1.2 排序的运用&#x1f49c; 2. 直接插入排序&#x1f7e2; 2.1 基本思想&#x1f7e9; 2.2 思路分析&#x1f49a; 2.3 代码实现✅ 2.3.1 sort.h 2.3.2 sort.c 2.3.3 test.c …

c语言-指针练习题

目录 前言一、题目一二、题目二总结 前言 为了巩固c语言中关于指针知识点的掌握&#xff0c;本篇文章记录关于指针的练习题。 一、题目一 有n个整数&#xff0c;使前面各数顺序往后移动m个位置&#xff0c;最后m个数变成最前面的m个数 写一函数实现以上功能&#xff0c;在主函…

k8s搭建(五、k8s可视化管理工具Dashboard配置)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

2000-2022年上市公司股票流动性指标数据/股票流动性Amihud(原始数据+计算代码+计算结果)

2000-2022年上市公司股票流动性指标数据/股票流动性Amihud&#xff08;原始数据计算代码计算结果&#xff09; 1、时间&#xff1a;2000-2022年 3、指标&#xff1a;证券代码_没有单位、交易日期_没有单位、日个股交易金额_元、考虑现金红利再投资的日个股回报率_没有单位、交…

杭电新生赛 大雪球 二分

&#x1f468;‍&#x1f3eb; 题目地址 ✨ AC code import java.io.*; import java.util.*;public class Main {static BufferedReader in new BufferedReader(new InputStreamReader(System.in));static BufferedWriter out new BufferedWriter(new OutputStreamWriter(Sy…

二叉树的中序遍历,力扣

目录 题目地址&#xff1a; 题目&#xff1a; 解题方法&#xff1a; 解题分析&#xff1a; 解题思路&#xff1a; 代码实现&#xff1a; 注&#xff1a; 代码实现&#xff08;递归&#xff09;&#xff1a; 代码实现&#xff08;迭代&#xff09;&#xff1a; 题目地址&#xf…

SpringBoot 增量/瘦身部署jar 包

背景 SpringBoot 项目的部署一般采用全量jar 包方式部署相关项目&#xff0c;如果我们对相关的Contrller\Service\Dao\Mapper 层进行相关业务调整就需要重新编译全量jar 包&#xff08;包大小约为200M左右&#xff09;实在太麻烦了。 本文:重点讲解使用SpringBoot 的增量/瘦身…

数字资产学习笔记

附&#xff1a;2023年数据资源入表白皮书下载&#xff1a; 关注WX公众号&#xff1a; commindtech77&#xff0c; 获得数据资产相关白皮书下载地址 1. 回复关键字&#xff1a;数据资源入表白皮书 下载 《2023数据资源入表白皮书》 2. 回复关键字&#xff1a;光大银行 下载 光…