JVM 基础篇:类加载器

一.了解JVM

1.1什么是JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟计算机功能来实现的,JVM屏蔽了与具体操作系统平台相关的信息,Java程序只需生成在Java虚拟机上运行的字节码,就可以在多种平台上不加修改的运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

虚拟机可以分为系统虚拟机程序虚拟机

  • 系统虚拟机是一种虚拟化技术,它模拟整个计算机硬件环境,包括处理器、内存、存储和外部设备。它的主要目标是在单个物理计算机上同时运行多个操作系统。每个虚拟机都具有独立的操作系统和应用程序,就像在不同的物理计算机上运行一样。系统虚拟机的例子包括VMware、VirtualBox和Hyper-V。
  • 程序虚拟机是一种虚拟化技术,它仅模拟计算机上的一个单独的应用程序运行环境,而不是整个操作系统。它的主要目标是提供一个独立的运行环境,使应用程序能够在不同的操作系统上运行而无需修改。程序虚拟机通常用于解决跨平台兼容性的问题,模拟一个应用程序的运行环境,使应用程序能够跨平台运行。常见的程序虚拟机包括Java虚拟机(JVM)。

1.2JRE/JDK/JVM

  • JDK(Java Development Kit) 是整个JAVA的核心,包括了Java运行环境JRE(Java Runtime Envirnment),一堆Java工具(javac/java/jdb等)和Java基础的类库(即Java API)。
  • JRE(Java Runtime Environment,Java运行环境), 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,主要包括 Java 虚拟机(JVM)、Java 基础类库(Class Library)。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)
  • JVM(Java Virtual Mechinal)是JRE的一部分,叫做JAVA虚拟机,它是整个java实现跨平台的最核心的部分,负责解释执行并运行字节码文件(.class)。

1.3JVM的功能

1.4常见的JVM

1.5JVM的整体结构

二.字节码文件

2.1字节码文件的组成

这里重点看一下基本信息、常量池和方法

2.1.1基本信息

Magic魔数

主副版本号

主版本号不兼容会引发以下错误:

2.1.2常量池

字节码文件中常量池的作用:避免相同的内容重复定义,节省空间。

我们看下面一个案例

public class ConstantPoolTest2 {public static final String a1 = "abc";public static final String a2 = "abc";public static final String abc = "abc";public static void main(String[] args) {ConstantPoolTest2 constantPoolTest = new ConstantPoolTest2();}
}

首先看字段,我们看a1的常量值实际上指向了常量池中的8号

这实际上是常量池中的一个String_info,但是里面并没有存储真正的字符串字面量,而是一个10号常量的索引

10号常量存储的就是真正的字符串字面量

为什么字段不直接存储常量池里字符串字面量的索引?而要先找String_info,然后再找字符串字面量

因为字节码文件被加载的时候会把常量池中String_info加载到字符串常量池中,所以String_info需要存一份引用

那为什么String_info里不直接存储字符串字面量,而是存一份索引?

字段中的变量名也可能要引用常量池里的字符串字面量,如果用String_info存储字符串字面量则不合理,因为字段中的变量名并不是一个字符串的对象

符号引用

2.1.3方法

字节码中的方法区域是存放字节码指令的核心位置,字节码指令的内容存放在方法的Code属性中。

注意:

  • iload是将值复制一份,局部变量表中的值还在
  • istore是将操作数栈的栈顶弹出,并放入局部变量表的某个位置,此时操作数栈的值是不存在的
i=i++的执行流程

iinc x by y:将局部变量表的x号位置增加y

很明显操作数栈中的值一直都是0,只要istore,那么局部变量表中的值也会被覆盖,所以最终i为0

i=++i的执行流程

由于iinc指令在iload指令之前,所以i的最终值是1

2.2字节码文件常用工具

2.2.1 Java 字节码的字节码查看器:javap -v

2.2.2 阿里arthas

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,大大提升线上问题排查效率。

详细请看官网:简介 | arthas (aliyun.com)

dashboard显示当前系统的实时数据面板,-i刷新实时数据的时间间隔 (ms),-n刷新实时数据的次数

dump已加载类的字节码文件到特定目录:

dump -d 特定目录  类的全限定名(即包名+类名) 

反编译已加载类,得到源码:

jad 类的全限定名(即包名+类名) 

三.类的生命周期

3.1加载阶段

  1. 类加载器ClassLoader根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息
  2. 类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到内存的方法区中。生成一个InstanceKlass对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。
  3. 同时,Java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象。作用是在Java代码中去获取类的信息以及存储静态字段的数据(JDK1.7 字符串常量池和静态变量从永久代移动了 Java 堆中)

对于开发者来说,只需要访问堆中的Class对象而不需要访问方法区中所有信息。这样Java虚拟机就能很好地控制开发者访问数据的范围。

加载阶段过后,字节码文件就已经被读取到了内存中,并且会创建一个代表该类的Class对象。

3.2连接阶段

  1. 第一个环节是验证,验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与。
  2. 准备阶段为静态变量(static)分配内存并设置初始值。准备阶段只会给静态变量赋初始值,而每一种基本数据类型和引用数据类型都有其初始值。但注意如果字段是final修饰的基本类型或者字符串常量(经过编译器优化),则会在准备阶段直接赋予最终值。
  3. 解析阶段主要是将常量池中的符号引用替换为直接引用。 符号引用就是在字节码文件中使用编号来访问常量池中的内容。 直接引用不再使用编号,而是使用内存中地址进行访问具体的数据。

3.3初始化阶段

初始化阶段会执行字节码文件中 clinit 部分的字节码指令。

<clinit>方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问

以下几种方式会导致类的初始化:

  1. 访问一个类的静态变量或者静态方法,注意变量是final修饰的并且等号右边是常量不会触发初始化
  2. 调用Class.forName(String className),注意 Class<?> forName(String name, boolean initialize, ClassLoader loader) 这个构造器可以指定不初始化
  3. new一个该类的对象时。
  4. 执行Main方法的当前类。

我们可以在JVM设置里添加 -XX:+TraceClassLoading 参数,这样可以看到有哪些类被加载

通过测试以下程序可以发现

类被加载时不一定会被初始化,而是在需要初始化的时候才初始化。

类加载但不初始化的情况

  • Class<?> forName(String name, boolean initialize, ClassLoader loader) 这个构造器可以指定不初始化
  • ClassLoader的loadClass(String className);方法也只会加载并编译某类,并不会对其执行初始化
  • 类名.class

面试题分析

clinit指令在以下情况下不会出现

  1. 无静态代码块且无静态变量赋值语句。
  2. 有静态变量的声明,但是没有赋值语句。如 public static int a;
  3. 静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。如 public final static int a = 1;

当出现继承关系时

  • 直接访问父类的静态变量,不会触发子类的初始化。
  • 子类的初始化clinit调用之前,会先调用父类的clinit初始化方法。

如果把new B02()去掉会怎么样呢?

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

四.类加载器

类加载器的设计JDK8和8之后的版本差别较大,JDK8及之前的版本中默认的类加载器有如下几种:

4.1启动类加载器(引导类加载器,根加载器,Bootstrap)

启动类加载器是最底层的类加载器,是虚拟机的一部分,它是由C++语言实现的,无法在Java代码中直接获取到,且没有父加载器(这里形容的是父子关系的层次结构,并非继承关系),也没有继承java.lang.lassLoader类。

它主要负责加载由系统属性 “sun.boot.cass.path” 指定的路径下的核心类库(即<JAVA_HOME>/jre/lib),包含了Object、String、Math、装箱类型、日期类等核心类

public class Demo {public static void main(String[] args) {//Bootstrap 引导类加载器//打印为null,是因为Bootstrap是C++实现的ClassLoader classLoader = Object.class.getClassLoader();System.out.println(classLoader);//查看引导类加载器会加载那些jar包URL[] urLs = Launcher.getBootstrapClassPath().getURLs();for (URL urL : urLs) {System.out.println(urL);}}
}

4.2扩展类加载器(ExtClassLoader)

  • 全类名:sum.misc.Launch$ExtClassLoader,Java语言实现。  
  • 扩展类加载器的父加载器是Bootstrap启动类加载器 (注:不是继承关系)  
  • 扩展类加载器负责加载<JAVA_HOME>\jre\lib\ext目录下的类库。

注: JDK9是jdk.internal.loader.ClassLoaders$PlatformClassLoader类

4.3应用程序类加载器(系统类加载器,AppClassLoader)

  • 全类名: sun.misc.Launcher$AppClassLoader  
  • 系统类加载器的父加载器是ExtClassLoader扩展类加载器 (注:不是继承关系)  
  • 系统类加载器负责加载 classpath环境变量所指定的类库,包括项目中自己编写的类文件以及第三方jar包中的类文件,是用户自定义类的默认类加载器。

注: JDK9是jdk.internal.loader.ClassLoaders$AppClassLoader类

4.4三者之间的关系

  • AppClassLoader的父加载器是ExtClassLoader
  • ExtClassLoader的父加载器是Bootstrap
  • Bootstrap是根加载器  
  • AppClassLoader和ExtClassLoader都实现了抽象类ClassLoader

三者之间是没有继承关系的,而是一种组合关系

  • 抽象类ClassLoader有一个字段parent, AppClassLoader和ExtClassLoader通过设置该字段引用,指定父加载器。(是组合关系)
  • AppClassLoader 的parent指向 ExtClassLoader
  • ExtClassLoader 的parent指向 null,(null的原因是因为Bootstrap是C++实现的,通过代码中逻辑判断来转向Bootstrap)

4.5双亲委派机制

双亲委派机制指的是:自底向上查找是否加载过,再由顶向下进行加载。

双亲委派机制的好处

  • 避免类的重复加载:当父加载器已经加载该类时,就没有必要子加载器再加载一遍,保证被加载类的唯一性。
  • 避免核心类篡改:通过双亲委派机制,让顶层的类加 载器去加载核心类,避免恶意代码 替换JDK中的核心类库,比如 java.lang.String,确保核心类 库的完整性和安全性。

我们可以看看下面的程序

这里我自定义了一个String类,并且类的全限定名和JDK内置的String类完全一样。但运行结果如下:

异常提示:在类 java.lang.String 中找不到 main 方法。

why?这里程序在执行时识别的是src中的java.lang.String,src就是classpath,因此会调用系统加载器。但根据双亲委派机制,系统加载器会逐层委派上层加载器来加载此类,在委派的时候,最上层的加载器是根加载器,即根加载器优先级最高。而根加载器能够在jre\lib\rt.jar包中找到一个重名的java.lang.String(即jdk自带的String),因此根据双亲委派最终会由最顶层的根加载器来执行jdk自带的java.lang.String。显然,jdk中的String并没有main()方法,因此报错找不到main()

4.6自定义类加载器

Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器都应该继承ClassLoader类。

loadClass默认实现如下:

再看看loadClass(String name, boolean resolve)函数,双亲委派机制的核心代码就位于这里

从上面代码可以明显看出,loadClass(String, boolean)函数即实现了双亲委派模型!整个大致过程如下:

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);)或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

整个调用过程如下图所示

在自定义ClassLoader的子类时候,我们常见的会有两种做法:

  • 重写loadClass()方法:这样会打破双亲委派模型,可能会导致一些Java的核心类无法加载,不建议重写
  • 重写findClass()方法:是在双亲委派模型的框架下进行小范围的改动,建议重写

实战代码如下:

public class MyClassLoader extends ClassLoader {private String root;@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException();} else {return defineClass(name, classData, 0, classData.length);}}private byte[] loadClassData(String className) {String fileName = root + File.separatorChar+ className.replace('.', File.separatorChar) + ".class";try {InputStream ins = Files.newInputStream(Paths.get(fileName));ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 1024;byte[] buffer = new byte[bufferSize];int length = 0;while ((length = ins.read(buffer)) != -1) {baos.write(buffer, 0, length);}return baos.toByteArray();} catch (IOException e) {e.printStackTrace();}return null;}public String getRoot() {return root;}public void setRoot(String root) {this.root = root;}public static void main(String[] args)  {MyClassLoader classLoader = new MyClassLoader();classLoader.setRoot("D:\\");Class<?> testClass = null;try {//需要为com.字节码文件.classloader.A 格式,否则defineClass方法会抛异常testClass = classLoader.loadClass("com.字节码文件.classloader.A");Object object = testClass.newInstance();System.out.println(object.getClass().getClassLoader());} catch (Exception e) {e.printStackTrace();}}
}

注意

  • 实例中的 class文件不能放在classpath下,否则根据双亲委派机制会被应用程序类加载器加载,而不会通过我们自定义类加载器来加载。
  • 这里传递的文件名需要是类的全限定性名称,即 com.字节码文件.classloader.A 格式的,因为 defineClass 方法是按这种格式进行处理的。

4.7Launcher类

AppClassLoader和ExtClassLoader是Launcher的静态内部类,在程序启动时JVM会创建Launcher对象,Launcher构造器会同时会创建扩展类加载器和应用类加载器。

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

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

相关文章

从Excel到智能化:智能报表的演进与未来发展趋势

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 报表的迭代历程 报表工具的诞生与计算机技术的出现和信息技术的进步密不可分。下图是报…

2017年高热度编程语言简介

世上语言千千万&#xff0c;我却独爱这一种!”这句话用来形容程序员和编程语言之间的爱恨情仇实在是再精准不过了。根据GitHub 2016年的开源报告&#xff0c;其上所有开源项目共包含了316种编程语言&#xff0c;这是一个什么概念呢?举个例子来说&#xff0c;世界上共有226个国…

粤嵌实训医疗项目day02(Vue + SpringBoot)

目录 一、创建vue项目并运行 二、vue-cli中的路由使用 三、element-ui框架、实现页面布局以及vue-路由 四、前端登录页面 五、user登录后端接口完善【后端】 六、user登录前端-请求工具-请求发起【前端】 七、请求的跨域-访问策略 八、完善项目的页面布局、导航菜单以及…

“香蕉大王”的转型升级,能否扩大市场份额?

佳农食品控股 ( 集团 ) 股份有限公司,于2023年10月11日同海通证券签署上市辅导协议&#xff0c;计划登陆上交所主板。据了解这已经不是佳农食品第一次IPO了&#xff0c;2019 年&#xff0c;佳农集团曾向上交所递交过招股说明书&#xff0c;当时的招股书披露&#xff0c;佳农集团…

python爬虫入门(一)web基础

HTTP基本要点 HTTP请求&#xff0c;由客户端向服务端发出&#xff0c;可以分为 4 部分内容&#xff1a;请求方法&#xff08;Request Method&#xff09;、请求的网址&#xff08;Request URL&#xff09;、请求头&#xff08;Request Headers&#xff09;、请求体&#xff08…

如何破解压缩包密码,CTF压缩包处理

I. 引言 压缩包我们经常接触&#xff0c;用于对文件进行压缩存储/传输。压缩包处理在CTF比赛中是非常重要的一块&#xff0c;因为压缩包中可能包含重要信息&#xff1a;许多CTF题目会将关键信息隐藏在压缩包中&#xff0c;参赛者需要解压并查看其中的内容才能获取有用的线索。…

使用Axure RP和内网穿透技术制作静态站点并实现公网访问

文章目录 前言1.在AxureRP中生成HTML文件2.配置IIS服务3.添加防火墙安全策略4.使用cpolar内网穿透实现公网访问4.1 登录cpolar web ui管理界面4.2 启动website隧道4.3 获取公网URL地址4.4. 公网远程访问内网web站点4.5 配置固定二级子域名公网访问内网web站点4.5.1创建一条固定…

如何在linux服务器上安装Anaconda与pytorch

如何在linux服务器上安装Anaconda与pytorch 1&#xff0c;安装anaconda1.1 下载anaconda安装包1.2 安装anaconda1.3 设计环境变量1.4 安装完成验证 2 Anaconda安装pytorch2.1 创建虚拟环境2.2 查看现存环境2.3 激活环境2.4 选择合适的pytorch版本下载2.5 检测是否安装成功&…

Centos磁盘爆满_openEuler系统磁盘爆满清理方法---Linux工作笔记060

磁盘爆满,监控部门就会报警,报警就要处理,但是程序员并不擅长做运维的工作,记录一下把...以后用到会方便: 使用df -h命令可以看到,对应的磁盘占用情况,这里我的/dev/mapper/openeuler-root这个目录 占用的磁盘比较多,到了百分之95了.. 往往就是这个跟目录,我这里/data目录是自…

CVer从0入门NLP(一)———词向量与RNN模型

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

[C++随想录] 二叉搜索树

搜素二叉树 二叉搜索树的使用二叉搜索树的模拟实现(K)整体结构循环版本递归版本 二叉搜索树的应用源码(kv) 二叉搜索树的使用 二叉搜索树 相较于 普通的二叉树来说: 根节点的左子树的所有键值都 小于 根节点, 根节点的右子树的所有键值 大于 根节点根节点的 左右子树 都是 二…

2023年中国石英矿资源现状及行业市场供需分析[图]

石英矿为常见的非金属矿物质&#xff0c;具有储量大、分布广、易开采等特点&#xff0c;根据不同成矿特性和理化特性&#xff0c;石英矿物可分为岩浆岩型、变质型、热液型、沉积型&#xff0c;对应的石英岩分别为花岗伟晶岩、脉石英岩、石英岩和石英砂岩。 石英矿物类型和特点…

无人机遥控中应用的2.4GHz无线芯片

无人驾驶飞机简称“无人机”&#xff0c;英文缩写为“UAV”&#xff0c;是利用无线电遥控设备和自备的程序控制装置操纵的不载人飞机&#xff0c;或者由车载计算机完全地或间歇地自主地操作。是一种不需要人操控就能够自主飞行的飞行器&#xff0c;它可以执行多种任务&#xff…

大数据开发中的秘密武器:探索Hadoop纠删码的奇妙世界

随着大数据技术的发展&#xff0c;HDFS作为Hadoop的核心模块之一得到了广泛的应用。为了系统的可靠性&#xff0c;HDFS通过复制来实现这种机制。但在HDFS中每一份数据都有两个副本&#xff0c;这也使得存储利用率仅为1/3&#xff0c;每TB数据都需要占用3TB的存储空间。因此&…

【网络安全入门】学习网络安全必须知道的100 个网络基础知识

前言 先领取资料再阅读哦 【282G】网络安全&黑客技术零基础到进阶全套学习大礼包&#xff08;附面试题答案&#xff09;&#xff0c;免费分享&#xff01; 【282G】网络安全&黑客技术零基础到进阶全套学习大礼包&#xff08;附面试题答案&#xff09;&#xff0c;免…

安装Docker

本安装教程参考Docker官方文档&#xff0c;地址如下&#xff1a;https://docs.docker.com/engine/install/centos/ 卸载旧版 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \ docker-client \ docker-client-latest \ docker-common…

【计算机网络】TCP 协议的相关特性

TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的协议。以下是TCP协议的相关特性&#xff1a; 可靠性&#xff1a;TCP通过确认和重传机制保证数据的可靠传输。 面向连接&#xff1a;TCP在传输数据前需要先建立连接。连接的建立过程包括三次握手…

C++ 读MTK代码 综测校准 PSU读开关电源电压或电流 visa

1.C定义dll接口 // The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the PSU_DLL_EXPORTS // symbol defined on the command line. this symbol should not b…

Zookeeper 和 Kafka 工作原理及如何搭建 Zookeeper集群 + Kafka集群

目录 1 Zookeeper 1.1 Zookeeper 定义 1.2 Zookeeper 工作机制 1.3 Zookeeper 特点 1.4 Zookeeper 数据结构 1.5 Zookeeper 应用场景 1.6 Zookeeper 选举机制 2 部署 Zookeeper 集群 2.1 安装前准备 2.2 安装 Zookeeper 3 Kafka 3.1 为什么需要消息队列&#xff08;…

防雷检测的项目和行业的等级区分

防雷检测是指对雷电防护装置的性能、质量和安全进行检测的活动&#xff0c;是保障人民生命财产和公共安全的重要措施。 地凯科技防雷检测的项目内容包括接闪器检测、引下线检测、接地装置检测、防雷区的划分、电磁屏蔽防雷检测、等电位连接检测、及电涌保护器 (SPD)性能检测。…