【jvm系列-02】jvm的类加载子系统以及jclasslib的基本使用

article/2025/9/28 21:23:38

JVM系列整体栏目


内容链接地址
【一】初识虚拟机与java虚拟机https://blog.csdn.net/zhenghuishengq/article/details/129544460
【二】jvm的类加载子系统以及jclasslib的基本使用https://blog.csdn.net/zhenghuishengq/article/details/129610963
【三】运行时私有区域之虚拟机栈、程序计数器、本地方发栈https://blog.csdn.net/zhenghuishengq/article/details/129684076

深入理解jvm的类加载器子系统

    • 1,jvm的内存结构
    • 2,类加载器加载过程
      • 2.1,加载阶段
      • 2.2,链接阶段
        • 2.2.1,验证
        • 2.2.2,准备
        • 2.2.3,解析
      • 2.3,初始化阶段(重点)
        • 2.3.1,jclasslib的安装
        • 2.3.2,clinit
        • 2.3.3,init
    • 3,类加载器
      • 3.1,类加载器的分类
      • 3.2,自定义类加载器的场景
      • 3.3,双亲委派机制
      • 3.4,自定义类加载器
      • 3.5,打破双亲委派模型
      • 3.6,其他

1,jvm的内存结构

在jvm的内存中结构中,其主要结构如下。

在这里插入图片描述

在jvm内部,需要将磁盘上的字节码文件通过这个类加载加载到内存中。在类加载子系统中,也需要经过一定的阶段将才能将这个文件加载到内存的运行时数据区中,如一些加载,验证,准备,解析,初始化等工作。在加载到运行时数据区之后,内部主要由一些共享的方法区、堆,以及私有的程序计数器、虚拟机栈、本地方法栈这些。这些字节码最终是需要通过执行引擎去执行的,执行引擎中主要包括解释器,JIT即时编译器,垃圾回收器等。

2,类加载器加载过程

在类加载器子系统中,主要会经过加载,链接和初始化三个阶段,链接又包括验证,准备和解析三个阶段,所以合起来就是加载,验证,准备,解析,初始化五个阶段。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GvV54MW4-1679014031170)(img/1678435559186.png)]

类加载器主要负责从文件系统或者网络中加载Class文件,并且类加载器只负责将文件加载,至于是否可以运行,还得由Execution Engine执行引擎决定。

2.1,加载阶段

加载阶段的加载器主要有引导类加载器,扩展类加载器,系统类加载器和自定义类加载器,主要是通过一个类的全限定名获取此类的二进制字节流,然后将这个字节流所代表的静态存储结构转化为方法区运行时的数据结构,然后在内存中生成一个java.lang.Class文件,作为方法区这个类的各种数据的访问入口。其主要就是将文件加载出来

常见的类加载方式有以下几种方式

  • 从本地系统直接加载
  • 从网络中获取
  • 从压缩包中获取,如zip
  • 运行时生成,如动态代理
  • 其他文件生成,典型的场景有:JSP应用
  • 数据库中获取 .class文件
  • 从加密文件中获取
  • 反射,序列化,克隆等

2.2,链接阶段

链接阶段又可以分为三个阶段,分别是验证,准备和解析

2.2.1,验证

验证的主要目的在于确保Class文件的字节流中所包含的信息符合当前虚拟机的要求,保证被加载类的正确性,不会危害虚拟机自身安全,相当于一种自我保护。如果编译器发现了有违法的信息之后,则编译器可以选择直接抛出异常或者拒绝编译。

主要包括四种验证:文件格式验证,元数据验证,字节码验证和符号引用验证。

2.2.2,准备

在准备阶段为类分配内存,并且设置该类的变量默认初始值,如整型的初始值为0。

public static int x = 10;  //在准备阶段赋值默认值为0,并且分配内存
public static void main(String[] args) {System.out.println(j);
}

这里主要是为变量进行一个默认的初始赋值,如果变量被static final修饰,那么这个变量会被变为常量,并且会在编译阶段就会分配内存。同时这里也不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中。

2.2.3,解析

就是将常量池内的符号引用转化为直接引用的过程,并随着JVM在执行完初始化之后再执行。

符号引用:以一组符号来描述引用的目标,只要能无歧义的定位到目标即可
直接引用: 相当于寻址的直接指针或者句柄

解析动作主要针对接口,类,字段,类方法,接口方法,方法类型,句柄和调用点限定符

2.3,初始化阶段(重点)

2.3.1,jclasslib的安装

在查看字节码文件之前,也可以在idea中安装查看对应的字节码指令的插件,在插件中搜索jclasslib即可,安装完成之后需要restart重启。

在这里插入图片描述

在安装完成之后,可以在view的位置来打开这个Bytecode字节码文件。

在这里插入图片描述

在点击这个Show Bytecode With Jclasslib 之后,就会出现以下的界面,会有一些版本,协议号,当前类,父类,接口数,文件数,方法数,属性数等。
在这里插入图片描述

2.3.2,clinit

初始化阶段就是执行类构造器方法()的过程,通过javac编译器自动收集类中的所有类变量赋值动作和静态代码块中的语句合并而来的。就是说这个clinit会将类变量的显示的初始化和静态代码块的初始化合并到一起,如果没有类变量的赋值操作或者静态代码块的赋值操作,那么这个clinit就不会出现在字节码文件中。

并且在整个流程中,变量的初始赋值是在这个准备阶段,而真正的赋值是在这个初始化阶段。

public static int x = 10;  //当前阶段中此时x的值为10

在这个Methods中,可以看到给这个类变量赋值,是有这个clinit的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3pjn0pen-1679014031174)(img/1678757187634.png)]

或者再静态代码块中给类变量赋值,也是可以有这个clinit的,可以看下图右边Methods中的第二点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kdaDvVLq-1679014031174)(img/1678757638769.png)]

如果该类具有父类,那么JVM会保证先加载父类的 ,再加载子类的 。并且在多线程中,虚拟机会保证一个类的 方法会加同步锁

2.3.3,init

在每个类中,都会有一个隐示的构造方法或者显示的构造方法,通过 来进行初始化。如在以下的代码中,显示的写了一个代码的构造器,先将初始值加载,或者再加载构造器里面的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4lwSq5B-1679014031175)(img/1678757995447.png)]

3,类加载器

3.1,类加载器的分类

在加载阶段中,主要有引导类加载器,扩展类加载器,应用程序类加载器和自定义加载器。在jvm中,规定支持两种类加载器,分別是引导类加载器和自定义类加载器,而扩展类和系统类都是属于自定义类加载器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9lAgwd4L-1679014031175)(img/1678767358793.png)]

并且在这几个来加载器中,这个引导类加载器是用c语言写的,而其他的类加载器都是使用这个JAVA语言写的。接下来通过代码查看一下这个类加载器,也可以发现这个引导类加载器不是java语言写的,所以获取不到,并且这个自定义类的加载器是通过系统类加载器来加载的。

//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(systemClassLoader);//获取上层扩展类加载器
ClassLoader extClassLoader = systemClassLoader.getParent();
//sun.misc.Launcher$ExtClassLoader@15615099
System.out.println(extClassLoader);//获取上层引导类加载器
ClassLoader bootStrapClassLoader = extClassLoader.getParent();
//null 尝试获取失败,该类由c语言编写
System.out.println(bootStrapClassLoader);//获取自定义类类加载器,以当前类为例
ClassLoader classLoader = ClassLoad.class.getClassLoader();
//sun.misc.Launcher$AppClassLoader@18b4aac2 
// 可以发现当前自定义类的了地价在其为系统类加载器
System.out.println(classLoader);

而像一些系统的核心类库,如String这种,是通过引导类加载器加载的。并且该加载器作为扩展类和系统类加载器的父类加载器,该加载器主要加载包名为java,javax,sun等开头的类

ClassLoader StringClassLoader = String.class.getClassLoader();
System.out.println(StringClassLoader);  //null

接下来可以获取一下这个引导类中,加载的全部内容

//获取引导类加载器可以加载的全部url
URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urLs.length; i++) {System.out.println(urLs[i]);
}

3.2,自定义类加载器的场景

一般情况使用引导类,扩展类和系统类是可以满足日常的开发需求的,但是在必要时,也可以手动自定义其他的类加载器。

引入自定义类加载器的原因

  • 隔离加载类
  • 修改类加载方式
  • 扩展加载源
  • 防止源码泄漏

自定义类加载器的实现步骤

  • 1,可以通过继承抽象类 java.lang.ClassLoader 类,实现自定义类加载器
  • 2,重写findClass()方法,然后将逻辑写在方法内部
  • 3,如果没有特别复杂的要求,可以直接继承URLClassLoader类

获取ClassLoader的途径

  • 获取当前类的ClassLoader:clazz.getClassLoader()
  • 获取上下文线程方式:Thread.currentThread.getContextClassLoader()
  • 获取系统的ClassLoader:ClassLoader.getSystemClassLoader()
  • 获取调用者的ClassLoader:DriverManager.getCallerClassLoader()

3.3,双亲委派机制

在jvm中,对class文件采用的是按需加载的方式,也就是说在需要使用到该类时才会加载,然后将class文件加载到内存生成class对象。并且java虚拟机采用的是一种双亲委派机制模式

其工作原理如下:

  • 1,如果一个类加载器收到了加载请求,他并不会自己去加载,而是将这个请求委托给父类加载器去执行
  • 2,如果父加载器还有其他的父加载器,那么会进一步的向上委托,一次递归到顶点
  • 3,如果父类可以完成任务,则将值返回;反之,则由子类尝试去加载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5LjzFfk-1679014031176)(img/1678776044698.png)]
其源码如下

// 检查当前类加载器是否已经加载了该类
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();//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类c = findClass(name);

通过源码也可以知道:
1,首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。

2,如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。

3,如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。ClassLoader的loadClass方法,里面实现了双亲委派机制。

双亲委派机制的好处

1,沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
2,避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

3.4,自定义类加载器

需要继承ClassLoader类,并且重写里面的findClass()方法。可以在本地磁盘里面创建一个类,如何加载的时候直接通过本地磁盘加载,而不需要使用到那几个类加载器加载,这样就完成了自定义类的加载器

import java.io.FileInputStream;
import java.lang.reflect.Method;/*** @Author: zhenghuisheng* @Date: 2023/3/16 23:09*/
public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}private byte[] loadByte(String name) throws Exception {name = name.replaceAll("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");int len = fis.available();byte[] data = new byte[len];fis.read(data);fis.close();return data;}protected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = loadByte(name);//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();throw new ClassNotFoundException();}}}/*** 下面的磁盘路径需要手动创建* @param args* @throws Exception*/public static void main(String args[]) throws Exception {//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoaderMyClassLoader classLoader = new MyClassLoader("D:/test");//D盘创建 test/com/zhenghuisheng/jvm 几级目录,将User类的复制类User.class丢入该目录//需要创建一个User类在这个路径下Class clazz = classLoader.loadClass("com.zhenghuisheng.jvm.User");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout", null);method.invoke(obj, null);System.out.println(clazz.getClassLoader().getClass().getName());}
}

3.5,打破双亲委派模型

应用程序里面有这个类,磁盘里面也有这个自定义的类,直接通过磁盘一次性加载,不需要利用到父类加载器,这样就打破了双亲委派机制。主要就是通过重写里面的findClass()方法,将里面的双亲委派机制的逻辑修改即可。让这个findClass直接找磁盘里面的路径,而不需要再写那些层层找父加载器加载即可。

3.6,其他

JVM中的两个class对象是否为同一个类

  • 类的完整名必须一致
  • 加载这个类的ClassLoader必须相同

在jvm中,即使这两个对象来源于同一个Class文件,被同一个虚拟机所加载,但只要加载他们的ClassLoader实例对象不一致,那么这两个类对象也是不相等的。


http://chatgpt.dhexx.cn/article/SGI80Lrs.shtml

相关文章

如何利用JClassLib修改.class文件

最近在学习逆向分析和反编译&#xff0c;无意之中了解到了JClassLib。JClassLib不但是一个字节码阅读器而且还包含一个类库允许开发者读取,修改,写入Java Class文件与字节码。其他的用途我就不说了&#xff0c;先看一下效果。 第一步、准备下载工具&#xff0c;一个是jd-gui&am…

直接修改别人jar包里面的class文件 工具:jclasslib

出于某些原因 需要把别人jar包里面的class修改一下信息 配置文件*.properties MANIFEST.MF 这些东西可以直接用记事本打开修改 然后替换掉就OK.. 在网上游荡了半天&#xff0c;没有找到合适的方法 开始我是先用jd-gui反编译 把我需要修改的那个A.class文件反编译出来把代码保…

Idea中jclasslib的安装与使用

我们学习JVM的时候常常需要查看字节码指令&#xff0c;而idea中就可以下载jclasslib插件&#xff0c;进行字节码指令的查看。下面我来带大家jclasslib的安装。 安装 安装之后重启即可 使用 使用的时候只需要点开view选中下图的选项即可&#xff0c;但是要注意是编译后再使用…

jclasslib安装

学习一个jvm的知识的时候总要去研究一些字节码指令&#xff0c; 但是每一次都把class文件打开到jclasslib里面很是麻烦&#xff0c;后来google发现有人已经写好了这个插件 1、 按住 ALTCTRLS 打开setting 2、 选择 plugins 3、选择 Browse Repositories ,搜索 jclasslib 由于…

jclasslib修改jar包中class文件 IDEA

一、需求&#xff1a; 第三方jar包中的代码不符合项目的需求&#xff0c;需要对某个class文件进行修改&#xff0c;从而满足项目的需求。通常采用对class文件进行反编译&#xff0c;然后在重新生成jar包。但是反编译的结果不能100%正确&#xff0c;所以直接对class文件进行修改…

可视化已编译Java类文件字节码的神器jclasslib

1、概述 作为Java工程师的你曾被伤害过吗&#xff1f;你是否也遇到过这些问题&#xff1f; 运行着的线上系统突然卡死&#xff0c;系统无法访问&#xff0c;甚至直接OOM 想解决线上JVM GC问题&#xff0c;但却无从下手 新项目上线&#xff0c;对各种JVM参数设置一脸茫然&#x…

[JVM] Jclasslib -- 可视化反编译.class文件的工具

传统我们使用以下命令即可反编译字节码文件 javap -v xx.class Jclasslib是一个可视化已编译Java类文件和包含的字节码的工具。可以在UI中编辑类文件的许多方面。此外&#xff0c;它还包含一个库&#xff0c;使开发人员能够读写Java类文件和字节码。它有两种使用方式--软件安…

jclasslib 插件安装及使用

学习Java&#xff0c;必须要了解 JVM&#xff0c;而学习 JVM 知识时&#xff0c;总要去研究一些字节码指令 而 jclasslib 这个插件很好的解决了这个问题 jclasslib安装&#xff1a; 1.打开设置 或者按Ctrl Alt S&#xff0c;打开设置 2.找到 jclasslib 插件 点击 install …

Python实现博弈树minmax补全与α-β剪枝算法脚本简介

文章目录 前言一、题目二、使用步骤1.递归构建博弈树2.α-β剪枝算法3.博弈树可视化4.测试实例5.结果展示6.全部代码 总结 前言 使用Python编程实现博弈树的构建&#xff0c;实现利用MinMax方法补全博弈树缺失值&#xff0c;并结合α-β剪枝算法&#xff0c;实现博弈树的剪枝。…

CART的剪枝算法

CART剪枝算法从“完全生长”的决策树的底端减去一些子树&#xff0c;使决策树变小&#xff0c;从而能够对未知数据有更准确的预测。CART算法由两步组成&#xff1a;首先从生成算法产生的决策树 底端开始不断剪枝&#xff0c;直到 的根结点&#xff0c;形成一个子树序列 &#x…

α-β剪枝算法学习寄(蒟蒻向,巨佬勿入)

由于做某题时暴力分出来很低&#xff0c;但某巨佬告诉我α-β剪枝很好用于是本屑踏上了征途。作为一只屑屑在学习这个算法时到处看各种blog&#xff0c;于是乎被上界下界决策等一众本屑看不懂的词汇弄得晕头转向&#xff0c;这篇blog就用本屑的语言梳理一下α-β剪枝算法捏。 …

alpha-beta剪枝算法原理(附代码)

alpha-beta剪枝算法原理 背景Max-Min算法alpha-beta剪枝代码 背景 由于笔者最近要写人工智能课的大作业&#xff0c;所以这两天在学习博弈论相关的知识&#xff0c;但网上对alpha-beta剪枝的原理讲的都不是很清晰&#xff0c;很多细节都忽略了&#xff0c;让初学者会有一种脑子…

人工智能之AlphaBeta剪枝算法

任务描述 本关任务&#xff1a;学习人工智能博弈算法中的 AlphaBeta 剪枝技巧&#xff0c;并基于 MinMax 算法编程实现如下图博弈树最优值问题的求解。 博弈树的输入形式为字符串&#xff1a;[A, [B, (E, 3), (F, 12), (G, 8)], [C, (H, 2), (I, 4), (J, 6)], [D, (K, 14), (…

α-β剪枝算法

在写之前首先感谢&#xff1a;https://blog.csdn.net/wenjianmuran/article/details/90633418 这里主要介绍minmax算法和α-β剪枝,相当于对一下文章的翻译&#xff1a; α-β剪枝 Minmax算法 正文&#xff1a; 看了很多关于α-β剪枝算法&#xff0c;大致明白了其中的含义…

α-β剪枝算法简单原理说明

看了一大堆文章实在看不懂&#xff0c;看视频也看不懂&#xff0c;但是看着看着突然顿悟了。这篇文章只讲大概的原理&#xff0c;不讲具体过程。 好了既然会搜这个算法&#xff0c;想必已经知道最大值最小值算法了&#xff08;不知道就去搜吧&#xff09;。这里直接讲例子。 …

alpha-beta剪枝算法

实验报告 alpha-beta剪枝算法 姓名&#xff1a;张楚明 学号&#xff1a;18342125 日期&#xff1a;2021.01.15 摘要 本实验将搜索深度为4的Alpha-Beta剪枝算法应用于中国象棋中黑方走棋&#xff0c;实现了中国象棋的人机博弈。博弈过程中综合考虑了棋力、对敌方棋子的攻击力、…

透析极大极小搜索算法和α-β剪枝算法(有案例和完整代码)

文章目录 前言minimax算法完整代码算法思想代码实现算法优化 α-β剪枝算法完整代码算法思想代码实现算法对比更多案例 结语 前言 先做了一版五子棋的小项目&#xff0c;后面又做了一个功能更强大的中国象棋的项目&#xff0c;但是始终都没有实现一版“智能”AI。 明知道这类博…

决策树后剪枝算法(二)错误率降低剪枝REP

​  ​​ ​决策树后剪枝算法&#xff08;一&#xff09;代价复杂度剪枝CPP  ​​ ​决策树后剪枝算法&#xff08;二&#xff09;错误率降低剪枝REP  ​​ ​决策树后剪枝算法&#xff08;三&#xff09;悲观错误剪枝PEP  ​​ ​决策树后剪枝算法&#xff08;四&…

C++实现的基于αβ剪枝算法五子棋设计

资源下载地址&#xff1a;https://download.csdn.net/download/sheziqiong/85883881 资源下载地址&#xff1a;https://download.csdn.net/download/sheziqiong/85883881 基于αβ剪枝算法的五子棋 五子棋介绍 简介&#xff1a; 五子棋是世界智力运动会竞技项目之一&#x…

决策树后剪枝算法(四)最小错误剪枝MEP

​  ​​ ​决策树后剪枝算法&#xff08;一&#xff09;代价复杂度剪枝CPP  ​​ ​决策树后剪枝算法&#xff08;二&#xff09;错误率降低剪枝REP  ​​ ​决策树后剪枝算法&#xff08;三&#xff09;悲观错误剪枝PEP  ​​ ​决策树后剪枝算法&#xff08;四&…