当前位置: 首页 > news >正文

【实战JVM】-基础篇-02-类的声明周期-加载器

【实战JVM】-基础篇-02-类的声明周期-加载器

  • 3 类的生命周期
    • 3.1 生命周期的概述
    • 3.2 加载阶段
      • 3.2.1 查看内存中的对象
    • 3.3 连接阶段
      • 3.3.1 验证阶段
        • 3.3.1.1 验证是否符合jvm规范
        • 3.3.1.2 元信息验证
        • 3.3.1.3 验证语义
        • 3.3.1.4 符号引用验证
      • 3.3.2 准备阶段
      • 3.3.3 解析阶段
    • 3.4 初始化阶段
      • 3.4.1 笔试题
      • 3.4.2 特殊情况
    • 3.5 总结
  • 4 类的加载器
    • 4.1 类加载器的分类
      • 4.1.1 JDK8之前的分类
      • 4.1.2 使用Arthas查看类加载器-classloader
      • 4.1.3 C++启动类加载器BootstrapClassLoader
      • 4.1.4 Java中默认类加载器
        • 4.1.4.1 扩展类加载器ExtClassLoader
        • 4.1.4.2 应用程序类加载器 AppClassLoader
        • 4.1.4.3 Arthas-classloader高级用法
    • 4.2 类加载器的双亲委派机制
      • 4.2.1 Arthas查看类加载器父子关系
      • 4.2.2 面试
    • 4.3 打破双亲委派机制
      • 4.3.1 自定义类加载器
        • 4.3.1.1 Arthas展示类的详细信息
        • 4.3.1.2 正确的自定义类加载器
      • 4.3.2 线程上下文类加载器
        • 4.3.2.1 SPI机制
        • 4.3.2.2 总结
      • 4.3.3 热部署
        • 4.3.3.1 热更新注意事项
    • 4.4 JDK8之后的类加载器


3 类的生命周期

3.1 生命周期的概述

在这里插入图片描述

3.2 加载阶段

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.2.1 查看内存中的对象

推荐使用JDK自带的hsdb工具查看Java虚拟机内部的内存信息。工具位于JDK安装目录下的lib文件夹的sa-jdi.jar中。

启动jvm项目的HsdbDemo

在这里插入图片描述

使用jps展示当前所有的java进程及id

jps

在这里插入图片描述

启动命令

D:\Software\software_with_code\idea\jdk\jdk1.8.0_381\lib>java -cp sa-jdi.jar sun.jvm.hotspot.HSDB

在这里插入图片描述

输入我们要找的类的编号HsdbDemo:18648

在这里插入图片描述

直接搜索HsdbDemo,因为我们只new了一次,自然只有一个对象。

在这里插入图片描述

在这里插入图片描述

一句话概括:类加载器将类的信息加载到内存中,java虚拟机在方法区和堆区中各分配一个对象去保存这个信息,而我们需要操作的则是堆区中的对象,jdk8之后静态字段也是存在堆中的

3.3 连接阶段

在这里插入图片描述

3.3.1 验证阶段

3.3.1.1 验证是否符合jvm规范

在这里插入图片描述

3.3.1.2 元信息验证

在这里插入图片描述

3.3.1.3 验证语义

在这里插入图片描述

3.3.1.4 符号引用验证

判断是否访问了其他类的private方法。

3.3.2 准备阶段

在这里插入图片描述
value在准备阶段分配的值是默认值0,而赋值为1是初始化阶段做的事

在这里插入图片描述

但是也有例外,如果是final修饰的基本数据类型,会在准备阶段直接将代码中的值进行赋值。

public static final int value=1;

3.3.3 解析阶段

在这里插入图片描述

直接引用不在使用编号,而是直接使用内存中的地址进行访问具体的数据。

3.4 初始化阶段

  • 初始化阶段会执行静态代码中的代码,并为静态变量赋值
  • 初始化阶段会执行字节码文件中的clinit部分的字节码指令。

在这里插入图片描述

如果颠倒一下顺序,那么输出则是1

在这里插入图片描述

因为静态变量是在连接阶段准备阶段完成默认初始化。然后再赋为2,再赋为1。

在这里插入图片描述

3.4.1 笔试题

(1)

在这里插入图片描述

构造代码块先于构造方法前执行

3.4.2 特殊情况

在这里插入图片描述

  • 直接访问父类的静态变量,不会触发子类的初始化。

  • 子类的初始化clinit调用之前,会先调用父类的clinit初始化方法。
    在这里插入图片描述

  • 数组的创建不会导致数组中元素的类进行初始化。

    • 是因为创建数组时是创建的数组的对象,而不是数组中元素的对象。所以数组中元素的类不会进行初始化。
  • 如果一个变量用final修饰,并且其中的内容要执行指令才能得出结果,那么会在clinit方法中进行初始化。

3.5 总结

在这里插入图片描述

4 类的加载器

类加载器是jvm提供给应用程序去实现获取类和接口字节码数据的技术。

负责在类加载过程中的字节码获取并且加载到内存这一部分。通过加载字节码数据放入内存转换成byte[],接下来调用虚拟机底层的方法将byte[]转换成方法区和堆中的数据。

4.1 类加载器的分类

在这里插入图片描述

俩下Shift是搜索
Ctrl+Alt+类是找当前类的所有实现

4.1.1 JDK8之前的分类

在这里插入图片描述

  • 引导类加载器 Bootstrap,加载属于JVM的一部分,由C++代码实现,负责加载<JAVA_HOME\>\jre\lib路径下的核心类库
  • 扩展类加载器 ExtClassLoader,扩展类加载器负责加载<JAVA_HOME>\jre\lib\ext目录下的类库。
  • 应用程序类加载器 AppClassLoader,应用程序类加载器负责加载 classpath环境变量所指定的类库,是用户自定义类的默认类加载器。

4.1.2 使用Arthas查看类加载器-classloader

在这里插入图片描述

启动Hsdbdemo后打开arthas,在arthas工作目录中启动

java -jar arthas-boot.jar

进入Hsdbdemo

classloader

在这里插入图片描述

4.1.3 C++启动类加载器BootstrapClassLoader

负责加载<JAVA_HOME\>\jre\lib路径下的核心类库

通过类名.class.getClassLoader来获取当前类的类加载器。

在这里插入图片描述

添加java虚拟机参数D:/jvm/jar/classloader-test.jar是jar包地址

-Xbootclasspath/a:D:/jvm/jar/classloader-test.jar

4.1.4 Java中默认类加载器

在这里插入图片描述

4.1.4.1 扩展类加载器ExtClassLoader

扩展类加载器 ExtClassLoader,扩展类加载器负责加载<JAVA_HOME>\jre\lib\ext目录下的类库。

在这里插入图片描述

添加java虚拟机参数D:/jvm/jar/classloader-test.jar是jar包地址。

不仅需要jar包的地址,还需要原来ext的地址D:\Software\software_with_code\idea\jdk\jdk1.8.0_381\jre\lib\ext

-Djava.ext.dirs="D:\Software\software_with_code\idea\jdk\jdk1.8.0_381\jre\lib\ext;D:/jvm/jar/classloader-test.jar"

在windows中;是追加,linux和mac中:是追加,尽量用双引号引起俩,以免因为特殊字符报错。

4.1.4.2 应用程序类加载器 AppClassLoader

应用程序类加载器 AppClassLoader,应用程序类加载器负责加载 classpath环境变量所指定的类库,是用户自定义类的默认类加载器。既可以加载当前项目中创建的类,也可以加载maven依赖中包含的类。

4.1.4.3 Arthas-classloader高级用法
classloader -l

在这里插入图片描述

查看当前所有的类加载器以及其哈希值

classloader -c hash值

在这里插入图片描述

查看当前查询的类加载器加载的所有jar包

在这里插入图片描述

4.2 类加载器的双亲委派机制

jvm中有多个类加载器,双亲委派机制的核心就是解决一个类到底由谁加载的问题。

双亲委派机制的作用

  1. 保证类加载的安全性

    通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如Java.lang.String,确保核心类库的完整性和安全性。

  2. 避免重复加载

    双亲委派机制可以避免同一个类被多次加载

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.2.1 Arthas查看类加载器父子关系

classloader -t

在这里插入图片描述

4.2.2 面试

在这里插入图片描述

4.3 打破双亲委派机制

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.3.1 自定义类加载器

在这里插入图片描述

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {if(name.startsWith("java.")){return super.loadClass(name);}byte[] data = loadClassData(name);return defineClass(name, data, 0, data.length);
}public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {BreakClassLoader1 classLoader1 = new BreakClassLoader1();classLoader1.setBasePath("D:\\lib\\");Class<?> clazz1 = classLoader1.loadClass("com.itheima.my.A");BreakClassLoader1 classLoader2 = new BreakClassLoader1();classLoader2.setBasePath("D:\\lib\\");Class<?> clazz2 = classLoader2.loadClass("com.itheima.my.A");System.out.println(clazz1 == clazz2);Thread.currentThread().setContextClassLoader(classLoader1);System.out.println(Thread.currentThread().getContextClassLoader());System.in.read();}

重写loadClass方法,删除双亲委派机制,如果是java开头的jar包,就交给原先父类的loadClass,如果是自定义的,就自己直接加载。

自定义类加载器没指定双亲的话,默认双亲为应用程序类加载器

在这里插入图片描述

4.3.1.1 Arthas展示类的详细信息
sc -d com.itheima.my.A

在这里插入图片描述

因为刚刚用两个不同的类加载器加载com.itheima.my.A,自然得到两个不同的对象

4.3.1.2 正确的自定义类加载器

在这里插入图片描述

4.3.2 线程上下文类加载器

在这里插入图片描述

DriverManager类位于rt.jar包中,由启动类加载器加载。

在这里插入图片描述

4.3.2.1 SPI机制

SPI全程Service Provider Interface,是JDK内置的一种服务提供发现的机制

需要在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件java.sql.Driver的接口,在这个文件中写入接口的实现类的全限定名com.mysql.cj.jdbc.Driver。

在这里插入图片描述

ServiceLoader这个类的源码如下:

public final class ServiceLoader<S> implements Iterable<S> {//扫描目录前缀private static final String PREFIX = "META-INF/services/";// 被加载的类或接口private final Class<S> service;// 用于定位、加载和实例化实现方实现的类的类加载器private final ClassLoader loader;// 上下文对象private final AccessControlContext acc;// 按照实例化的顺序缓存已经实例化的类private LinkedHashMap<String, S> providers = new LinkedHashMap<>();// 懒查找迭代器private java.util.ServiceLoader.LazyIterator lookupIterator;// 私有内部类,提供对所有的service的类的加载与实例化private class LazyIterator implements Iterator<S> {Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;String nextName = null;//...private boolean hasNextService() {if (configs == null) {try {//获取目录下所有的类String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = loader.getResources(fullName);} catch (IOException x) {//...}//....}}private S nextService() {String cn = nextName;nextName = null;Class<?> c = null;try {//反射加载类c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {}try {//实例化S p = service.cast(c.newInstance());//放进缓存providers.put(cn, p);return p;} catch (Throwable x) {//..}//..}}
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在线程中使用

Thread.currentThread().getContextClassLoader()

默认获得的是应用程序类加载器

4.3.2.2 总结

在这里插入图片描述

在这里插入图片描述

所以说,打破双亲委派机制的唯一方法就是重写loadClass或者findClass方法

4.3.3 热部署

在这里插入图片描述

  1. 启动SpringbootClassfileApplication后,使用arthas进入,反编译出UserController

    java -Dfile.encoding=UTF-8 -jar arthas-boot.jar
    
    jad --source-only com.itheima.springbootclassfile.controller.UserController > "D:\Code\JavaCode\JVM\hot-replace\UserController.java"
    

在这里插入图片描述

修改为

if (type.equals(UserType.REGULAR.getType())) {
  1. 编译成字节码文件,如果直接编译,则会因为找不到类加载器而报错,所以需要先找到UserController.java的类加载器,获取其哈希值。

    sc -d com.itheima.springbootclassfile.controller.UserController
    

    在这里插入图片描述

  2. mc -c指定类加载器的哈希码 -d指定输出目录

    mc -c 18b4aac2 "D:\Code\JavaCode\JVM\hot-replace\UserController.java" -d "D:\Code\JavaCode\JVM\hot-replace"
    

    在这里插入图片描述

  3. 通过retransform

    retransform "D:\Code\JavaCode\JVM\hot-replace\com\itheima\springbootclassfile\controller\UserController.class"
    

    在这里插入图片描述

  4. 用jad查看热部署是否完成

    在这里插入图片描述

  5. 发送http请求

    在这里插入图片描述

    说明热部署已经完成

4.3.3.1 热更新注意事项

在这里插入图片描述

4.4 JDK8之后的类加载器

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

应用类加载器和扩展类加载器都是继承关系从URLClassLoader变为BuiltinClassLoader,没有太大的区别。

相关文章:

  • 春秋CVE-2022-23906
  • ❤职场小心得❤
  • 上交提出TrustGAIN,提出6G网络中可信AIGC新模式!
  • php质量工具系列之paslm
  • 工博科技联手伯尼纳,共谋食品包装外贸行业新市场,助力全球市场拓展!
  • 质量源于设计:QbD培训引领企业产品质量飞跃!
  • 数据库编程
  • 周报 | 24.5.20-24.5.26文章汇总
  • Python函数式编程入门窥探
  • 高弹性架构的微服务设计模式
  • 什么样的跨网文件交换系统适合车企行业?
  • 【Javascript】Promise形象比喻
  • Java的逻辑控制和方法的使用介绍
  • springboot+minio 文件上传
  • 【随笔4】心情——复杂
  • 【347天】每日项目总结系列085(2018.01.18)
  • Fabric架构演变之路
  • JAVA多线程机制解析-volatilesynchronized
  • React组件设计模式(一)
  • SQLServer之创建数据库快照
  • 百度地图API标注+时间轴组件
  • 从setTimeout-setInterval看JS线程
  • 计算机常识 - 收藏集 - 掘金
  • 今年的LC3大会没了?
  • 理解在java “”i=i++;”所发生的事情
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 微信开放平台全网发布【失败】的几点排查方法
  • 用Canvas画一棵二叉树
  • 在Unity中实现一个简单的消息管理器
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • gunicorn工作原理
  • 国内开源镜像站点
  • 组复制官方翻译九、Group Replication Technical Details
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • # Redis 入门到精通(一)数据类型(4)
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • ### RabbitMQ五种工作模式:
  • #14vue3生成表单并跳转到外部地址的方式
  • #includecmath
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • (33)STM32——485实验笔记
  • (C)一些题4
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (Redis使用系列) Springboot 整合Redisson 实现分布式锁 七
  • (ZT)薛涌:谈贫说富
  • (八)c52学习之旅-中断实验
  • (办公)springboot配置aop处理请求.
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (转)jdk与jre的区别
  • (自用)gtest单元测试
  • .NET 5种线程安全集合
  • .NET 8 跨平台高性能边缘采集网关
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析