HotSpot虚拟机之类加载过程及类加载器

news2024/12/26 9:31:19

目录

一、类加载过程

1. 加载(Loading)

2. 验证(Verification) 

3. 准备(Preparation) 

4. 解析(Resolution)

5. 初始化(Initialization)

二、类加载时机

1. 主动引用(引用时主动初始化)

2. 被动引用(引用时不触发初始化) 

1):第一种被动引用

2):第二种被动引用

3):第三种被动引用

三、类加载器

1. 加载器类型

2. 双亲委派模型

3. 破坏双亲委派

四、参考资料


一、类加载过程

        一个类型从被加载到JVM内存中开始,到卸载出内存为止,它的生命周期有7个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading),如下图所示。其中加载、验证、准备、初始化、卸载的开始具有顺序性,但可以交叉进行。以下详细介绍其各个阶段。

1. 加载(Loading)

        加载(Loading)目的是将类的Class文件加载到内存,由类加载器完成加载。

2. 验证(Verification) 

        验证(Verification)目的确保Class文件包含的信息符合《Java 虚拟机规范》的全部约束要求,验证阶段是否严谨,直接决定JVM是否能承受恶意代码的攻击。注意:文件格式验证后才能存储到元空间,其他三个验证基于元空间存储结构上进行,不再读取字节流

3. 准备(Preparation) 

        准备(Preparation) 目的将类中静态变量(static)分配内存并设置初始值(默认值)

4. 解析(Resolution)

        解析(Resolution)目的将常量池的符号引用替换为直接引用。引用有:

  • 符号引用:一组符号来描述引用的目标(与内存布局无关);
  • 直接引用:直接指向目标的指针、偏移量、目标的句柄(与内存布局有关)

        解析点主要是类或接口、字段、类方法、接口方法等,其中:JDK9之前除了接口方法(public修饰)无需进行访问权限校验,其他查找结束后都要进行访问权限校验,不通过则抛出:java.lang.IllegalAccessError,JDK9之后增加了模块化访问权限;除invokedynamic指令外,会对第一次解析结果进行缓存,称为“解析缓存”

解析点

解析步骤

类或接口

当前代码处在D类,根据未解析的符号引用N去解析类或接口C的直接引用,步骤如下:

step1:C非数组类型,全限定名N传递给D的类加载器,去加载C,根据C的加载动作,也可能触发其他类的相关加载动作,一旦有任何异常,则解析失败

step2:C数组类型,若元素是引用对象,则根据step1进行加载解析;

                                 若元素是基本类型,则JVM自动生成代表该数组维度和元素类型

step3:确认D是否对C有访问权限,否则抛出java.lang.IllegalAccessError;

step4:没有任何异常,则已形成有效的类或接口。

字段

当前代码处在D类,根据未解析的符号引用N去解析字段(字段所属的类或接口C),步骤如下:

step1:首先解析D字段表class_index的索引CONSTANT_Class_info类进行解析(字段所在的类解析),后续对C进行字段搜索;

step2:C本身包含了简单名称和描述符与目标字段匹配,返回该字段的直接引用,则查找结束;

step3:否则,则查找C实现的接口和其父接口,从下往上递归搜索,有匹配,则查找结束;

step4:否则,若C不是Object类,则查找C的父类,从下往上递归搜索,有匹配,则查找结束;

step5:否则,查找失败,则抛出java.lang.NoSuchFieldError;

step6:查找成功后,D是否对该字段有访问权限,否则抛出:java.lang.IllegalAccessError。

类方法

当前代码处在D类,根据未解析的符号引用N去解析方法(方法所属的类C),步骤如下:

step1:首先解析D方法表class_index的索引CONSTANT_Class_info类进行解析(字段所在的类解析),若C是一个接口(与接口方法相反),则抛出:java.lang.IncompatibleClassChangeError;

step2:在类C本身包含了简单名称和描述符与目标方法匹配,返回该方法的直接引用,则查找结束; 

step3:否则,若C不是Object类,则查找C的父类,从下往上递归搜索,有匹配,则查找结束;

step4:否则,则查找C实现的接口和其父接口,从下往上递归搜索,有匹配说明C是个抽象类,则抛出:java.lang.AbstractMethodError

step5:否则,查找失败,则抛出java.lang.NoSuchMethodError;

step6:查找成功后,是否对该方法有访问权限,否则抛出:java.lang.IllegalAccessError。

接口方法

当前代码处在D类,根据未解析的符号引用N去解析方法(方法所属的接口C),步骤如下:

step1:首先解析D方法表class_index的索引CONSTANT_Class_info类进行解析(字段所在的类解析),若C是一个类(与类方法相反),则抛出:java.lang.IncompatibleClassChangeError;

step2:在接口C本身包含了简单名称和描述符与目标方法匹配,返回该方法的直接引用,则查找结束;

step3:否则,则查找C的父接口,从下往上递归搜索直至Object类(接口方法包含Object类中方法),有匹配,则查找结束;

step4:否则,则查找C实现的接口和其父接口,从下往上递归搜索,若不同的父接口中有多个匹配的方法时,返回其中一个方法,则查找结束;

step5:否则,查找失败,则抛出java.lang.NoSuchMethodError。

注意:

    a.JDK9之前除了接口方法(public修饰)无需进行访问权限校验,其他查找结束后都要进行访问权限校验,不通过则抛出:java.lang.IllegalAccessError;JDK9之后增加了模块化访问权限;

    b.类方法和接口方法存储的地方不同:

       类方法CONSTANT_Methodref_info、接口方法CONSTANT_InterfaceMethodref_info

5. 初始化(Initialization)

        初始化(Initialization)目的是执行类构造器<clinit>()方法的过程类初始化<clinit>()在编译期自动生成,且自动收集类变量并赋值和静态语句块的语句合并产生

        注意:类构造器<clinit>()方法,先隐式调用父类该方法;而实例构造器<init>()方法,则显式调用父类构造器

二、类加载时机

1. 主动引用(引用时主动初始化)

        主动引用是引用时主动初始化,有且只有6种需立即初始化,如下图所示。

2. 被动引用(引用时不触发初始化) 

        被动引用是引用时不触发初始化,如下三种情况:

1):第一种被动引用

package com.cmmon.instance.classLoad;

/**
 * @author tcm
 * @version 1.0.0
 * @description 被动引用:引用时不触发初始化 —— 子类引用父类的静态字段,不会导致子类初始化
 * @date 2023/5/18 10:22
 **/
public class NotInitialization {

    /**
     * 静态字段时,只有直接定义这个字段的类才会被初始化
     * 因此:通过子类来引用父类定义的静态字段,则只会初始化父类而不初始化子类
     *
     * 添加参数:-XX:+TraceClassLoading会导致子类加载
     * [Loaded com.cmmon.instance.classLoad.SuperClass from file:/E:/idea%20project/instance-test/target/test-classes/]
     * [Loaded com.cmmon.instance.classLoad.SubClass from file:/E:/idea%20project/instance-test/target/test-classes/]
     */
    public static void main(String[] args) {
        System.out.println(SubClass.value);
        /*
            结果:并没有初始化子类SubClass
            SuperClass init!
            1234
         */
    }

}
class SuperClass {

    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 1234;

}

class SubClass extends SuperClass {

    static {
        System.out.println("SubClass init!");
    }

}

2):第二种被动引用

package com.cmmon.instance.classLoad;

/**
 * @author tcm
 * @version 1.0.0
 * @description 被动引用:引用时不触发初始化 —— 数组来引用类,不会导致该类的初始化
 * @date 2023/5/18 11:40
 **/
public class NotInitialization2 {

    /**
     * 通过数组来引用类,不会导致该类的初始化
     */
    public static void main(String[] args) {
        SuperClass2[] sca = new SuperClass2[10];
        /*
            没有输出:SuperClass init!
         */
    }

}

class SuperClass2 {

    static {
        System.out.println("SuperClass init!");
    }

    public static int value = 1234;

}

3):第三种被动引用

package com.cmmon.instance.classLoad;

/**
 * @author tcm
 * @version 1.0.0
 * @description 被动引用:引用时不触发初始化 —— 常量在编译阶段会加入到调用类的常量池中,不会导致常量所在的类初始化
 * @date 2023/5/18 17:33
 **/
public class NotInitialization3 {

    /**
     * 编译阶段通过常量传播优化后,常量直接存入到调用类的常量池中,不会导致常量所在的类初始化
     * 因此:NotInitialization3与ConstClass两个类在编译成Class文件后,则不存在任何联系了
     */
    public static void main(String[] args) {
        System.out.println(ConstClass.HELLO_WORLD);
        /*
            没有输出:ConstClass init!
         */
    }

}

class ConstClass {

    static {
        System.out.println("ConstClass init!");
    }

    public static final String HELLO_WORLD = "Hello World";

}

三、类加载器

1. 加载器类型

        类加载过程中加载(Loading)阶段是由类加载器完成,如下表所示根据不同层面对类加载器进行分类。注意:类的唯一性由类全限定名且类加载器共同决定,每个类加载器都一个独立的类名称空间

分类层面

分类

特点

JVM角度分类

启动类加载器

(Bootstrap ClassLoader)

1.JVM自身的一部分

2.由C++实现

其他类加载器

1.全部继承java.lang.ClassLoader

2.由Java语言实现

三层类加载器

(JDK8与之前版本)

启动类加载器

(Bootstrap ClassLoader)

1.负责加载${JAVA_HOME}/lib目录的类库

2.该加载器被Java程序直接引用,JVM自身的一部分。

扩展类加载器

(Extension ClassLoader)

1.负责加载${JAVA_HOME}/lib/ext目录的类库

2.由sun.misc.Launcher.ExtClassLoader类实现;

应用程序类加载器

(Application ClassLoader)

1.负责加载用户类路径classpath路径下的所有类库

2.由sun.misc.Launcher.AppClassLoader类实现;

自定义类加载器

(User ClassLoader)

用户自定义类加载器

模块化类加载器

(JDK9)

与JDK8有些变动

1.平台类加载器(PlatForm ClassLoader)代替扩展类加载器

2.取消${JAVA_HOME}/lib/ext、${JAVA_HOME}/jre目录;

3.平台类加载器和应用程序类加载器不在继承java.net.URLClassLoader,而继承BuiltinClassLoader。

注意:

     a.类的唯一性:类的全限定名 + 类加载器,每个类加载器都一个独立的类名称空间

     b.类加载器之间的层次关系,称为:“双亲委派模型”(Parents Delegation Model);

     c.父类加载器是否为null,来引导类加载器达到双亲委派,为null则使用启动类加载器

        如下代码所示,比较两个类是否相等:是否同一个全限定名的类 + 类加载器相同。

package com.cmmon.instance.classLoad;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author tcm
 * @version 1.0.0
 * @description 每个类加载器拥有一个独立的类名称空间
 * @date 2023/5/24 14:17
 **/
public class ClassLoadNamespace {

    /**
     * 比较两个类是否相等:是否同一个全限定名的类 + 类加载器相同
     * 每个类加载器拥有一个独立的类名称空间
     * 两个类的“相等”:类的Class对象的哈希码、equals()、isAssignableFrom()、instanceof
     */
    public static void main(String[] args) throws Exception {
        // 自定义类加载器
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }

                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        Object object = myLoader.loadClass("com.cmmon.instance.classLoad.ClassLoadNamespace").newInstance();
        System.out.println(object.getClass());  // class com.cmmon.instance.classLoad.ClassLoadNamespace
        System.out.println(object instanceof com.cmmon.instance.classLoad.ClassLoadNamespace); // false
        System.out.println(object.getClass().getClassLoader()); // com.cmmon.instance.classLoad.ClassLoadNamespace$1@7b3300e5

        Object object2 = new ClassLoadNamespace();
        System.out.println(object2.getClass()); // class com.cmmon.instance.classLoad.ClassLoadNamespace
        System.out.println(object2 instanceof com.cmmon.instance.classLoad.ClassLoadNamespace); // true
        System.out.println(object2.getClass().getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2
    }

}

2. 双亲委派模型

        如下图所示,双亲委派执行过程及其优点。JDK9维持三层类加载器和双亲委派,变动了委派给父类时,先判定该类是否归属到哪一个模块中,如有则委派给负责该模块的加载器

        父类加载器是否为null,来引导类加载器达到双亲委派,为null则使用启动类加载器。核心方法为java.lang.ClassLoader#loadClass(java.lang.String, boolean),如下源代码所示。

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;
	}
}

3. 破坏双亲委派

        双亲委派模型并不是强制约束的模型,而是Java设计器推荐给开发者的类加载实现方式。所以Java中大部分类加载器都遵循双亲委派模型,但也有例外,如:Tomcat、OSGi(动态模块化规范 _ 灵活的类加载器架构)。

        Tomcat6之前的类库目录如下图所示。

        Tomcat服务器加载器架构及对应的委派关系如下。

四、参考资料

HotSpot虚拟机之Class文件及字节码指令_爱我所爱0505的博客-CSDN博客

jvm之java类加载机制和类加载器(ClassLoader)的详解_超级战斗王的博客-CSDN博客

java类加载机制_xiaolong_java的博客-CSDN博客

类加载器分类_类加载器的分类_.小鲤鱼的博客-CSDN博客

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实践教程|基于 pytorch 实现模型剪枝

PyTorch剪枝方法详解&#xff0c;附详细代码。 一&#xff0c;剪枝分类 1.1&#xff0c;非结构化剪枝 1.2&#xff0c;结构化剪枝 1.3&#xff0c;本地与全局修剪 二&#xff0c;PyTorch 的剪枝 2.1&#xff0c;pytorch 剪枝工作原理 2.2&#xff0c;局部剪枝 2.3&#…

MySQL学习笔记之MySQL5.7用户管理

文章目录 用户创建用户修改修改用户名修改密码修改自己的密码修改其他用户的密码 删除用户权限管理查看所有权限授予权限回收权限权限表columns_privprocs_privtables_priv 用户创建 基本格式&#xff1a;create user 用户名 identified by 密码; mysql> create user szc …

[静态时序分析简明教程(十)]组合电路路径set_max/min_delay

静态时序分析简明教程-组合电路路径 一、写在前面1.1 快速导航链接 二、组合电路路径2.1 SDC约束2.2 举例 三、总结 一、写在前面 一个数字芯片工程师的核心竞争力是什么&#xff1f;不同的工程师可能给出不同的答复&#xff0c;有些人可能提到硬件描述语言&#xff0c;有些人…

无涯教程-Perl - recv函数

描述 This function receives a message on SOCKET attempting to read LENGTH bytes, placing the data read into variable SCALAR.The FLAGS argument takes the same values as the recvfrom( ) system function, on which the function is based. When communicating wit…

【c语言】字符函数与字符串函数(上)

大家好呀&#xff0c;今天给大家分享一下字符函数和字符串函数&#xff0c;说起字符函数和字符串函数大家会想到哪些呢&#xff1f;&#xff1f;我想到的只有求字符串长度的strlen,拷贝字符串的strcpy,字符串比较相同的strcmp,今天&#xff0c;我要分享给大家的是我们一些其他的…

② vue模板语法

文本绑定 普通文本渲染{{ }} 静态的文本绑定 v-html 动态的文本绑定 b-bind&#xff08;:&#xff09; 动态的属性绑定&#xff0c;可简写&#xff08;比如&#xff1a;v-bind:id :id&#xff09; 列表渲染 v-for 要有一个唯一id :key"item.id"(没有id就…

for macOS-21.1.0.3267中文直装版功能介绍及系统配置要求

FL Studio 21简称FL水果软件,全称是&#xff1a;Fruity Loops Studio编曲&#xff0c;由于其Logo长的比较像一款水果因此&#xff0c;在大家更多的是喜欢称他为水果萝卜&#xff0c;FL studio21是目前最新的版本&#xff0c;这是一款可以让你的计算机就像是一个全功能的录音室&…

GPT-NER:使用大型语言模型进行命名实体识别

讲在前面&#xff0c;chatgpt出来的时候就想过将其利用在信息抽取方面&#xff0c;后续也发现了不少基于这种大语言模型的信息抽取的论文&#xff0c;比如之前收集过的&#xff1a; https://github.com/cocacola-lab/GPT4IE https://github.com/RidongHan/Evaluation-of-ChatG…

p5.js 渐变填充的实现方式

theme: smartblue 本文简介 p5.js 作为一款艺术类的 canvas 库&#xff0c;对颜色方面的支持是挺下功夫的&#xff0c;比如本文要介绍的渐变方法。 lerpColor() 要实现渐变效果&#xff0c;可以使用 lerpColor() 方法。 lerpColor 的作用是混合两个颜色以找到一个介于它们之间的…

QGraphicsView实现简易地图5『经纬网格』

前文链接&#xff1a;QGraphicsView实现简易地图4『局部加载-地图漫游』 由于GCJ02 Web 墨卡托投影 纬度并不随像素等分&#xff0c;且两极跨度较大&#xff0c;因此本次演示采用的经纬网等分逻辑为等分像素。同等像素跨度之间&#xff0c;两级纬度变化较小&#xff0c;越靠近赤…

项目介绍:《WeTalk》网页聊天室 — Spring Boot、MyBatis、MySQL和WebSocket的奇妙融合

目录 引言&#xff1a; 前言&#xff1a; 技术栈&#xff1a; 主要功能&#xff1a; 功能详解&#xff1a; 1. 用户注册与登录&#xff1a; 2. 添加好友 3. 实时聊天 4. 消息未读 5. 删除聊天记录 6. 删除好友 未来展望&#xff1a; 项目地址&#xff1a; 结语&am…

IDEA关闭项目,但是后台程序没有关闭进程(解决方案)

最近遇到一个很奇怪的问题&#xff0c;idea关闭项目后&#xff0c;系统进程没有杀死进程&#xff0c;再次执行的时候会提示端口占用&#xff0c;并提示Process exited with an error: 1 (Exit value: 1) 错误原因&#xff1a;应用程序关闭后&#xff0c;进程不能同步关闭 解决方…

Python 使用Hadoop 3 之HDFS 总结

Hadoop 概述 Hadoop 是一个由Apache 软件基金会开发的分布式基础架构。用户可以在不了解分布式底层细节的情况下&#xff0c;开发分布式程序&#xff0c;充分利用集群的威力进行高速运算和存储。 Hadoop 实现一个分布式文件系统&#xff08;Hadoop Distributed File Sy…

短肥网络的 RTT 敏感性

周二下班路上发了一则朋友圈&#xff1a; 长肥管道的特征和问题谈得够多了&#xff0c;但这里谈的是短肥管道&#xff0c;因为下面趋势&#xff0c;短肥管道才是未来大势&#xff1a; 云计算致使数据中心网络快速发展&#xff0c;而数据中心网络时延短&#xff0c;带宽大。CD…

尼科彻斯定理-C语言/Java

描述 验证尼科彻斯定理&#xff0c;即&#xff1a;任何一个整数m的立方都可以写成m个连续奇数之和。 例如&#xff1a; 1^31 2^335 3^37911 4^313151719 输入一个正整数m&#xff08;m≤100&#xff09;&#xff0c;将m的立方写成m个连续奇数之和的形式输出。&…

代码详解——Transformer

文章目录 整体架构Modules.pyScaledDotProductAttention SubLayers.pyMultiHeadAttentionPositionwiseFeedForward Layers.pyEncoderLayerDecoderLayer Models.pyget_pad_maskget_subsequent_maskPositionalEncodingEncoderDecoderTransformer 整体架构 源码地址&#xff08;py…

传输控制协议TCP

目录 TCP报文格式 TCP的特点 TCP原理: 1.确认应答机制 2.超时重传机制 3.连接管理机制 建立连接 ​编辑关闭连接 4.滑动窗口机制 ​5.流量控制 6.拥塞控制 7.延迟应答 8.捎带应答 TCP报文格式 1.源端口号:发送端的哪一个端口发出的 2.目的端口号:接收端的哪一个端…

【深度学习】遗传算法[选择、交叉、变异、初始化种群、迭代优化、几何规划排序选择、线性交叉、非均匀变异]

目录 一、遗传算法二、遗传算法概述2.1 选择2.2 交叉2.3 变异 三、遗传算法的基本步骤3.1 编码3.2 初始群体的生成3.3 适应度评估3.4 选择3.5 交叉3.6 变异3.7 总结 四、遗传算法工具箱4.1 initializega4.2 ga4.3 normGeomSelect4.4 arithXover4.5 nonUnifMutation 五、遗传算法…

【Transformer】自注意力机制Self-Attention | 各种网络归一化Normalization

1. Transformer 由来 & 特点 1.1 从NLP领域内诞生 "Transformer"是一种深度学习模型&#xff0c;首次在"Attention is All You Need"这篇论文中被提出&#xff0c;已经成为自然语言处理&#xff08;NLP&#xff09;领域的重要基石。这是因为Transfor…

苹果电脑 Java切换版本

效果 1、安装 Java1.8和Java11 直接官网下载并安装 2、安装后的文件 /资源库/Java/JavaVirtualMachines/ 3、修改配置文件 vi ~/.bash_profile#java export JAVA_8_HOME"/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home" alias jdk8expor…