JVM - 字节码文件详解

2024-09-08 06:12
文章标签 java jvm 详解 字节

本文主要是介绍JVM - 字节码文件详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

目录

文章目录

1. 无关性基石

2. Class类文件结构

magic- 魔数

主副版本号

常量池

访问标志

类索引,父类索引与接口索引集合

字段

方法

属性

3. 类加载机制

类的生命周期

类加载过程

加载

连接

验证

准备

解析

初始化

4. 类加载器

类与类加载器

类加载器的分类

启动类加载器 

扩展类加载器

应用程序类加载器

双亲委派模型

打破双亲委派模型

打破双亲委派机制–自定义类加载器

打破双亲委派机制的第二种方法

打破双亲委派机制的第三种方法

总结


1. 无关性基石

Java在刚刚诞生之时曾经提出过一个非常著名的宣传口号 "一次编写, 到处运行"。它的底气就是Oracle公司和其他虚拟机发布商发布的许多虚拟机都可以运行在各种不同的硬件平台和操作系统上的虚拟机, 这些虚拟机都可以载入一种平台无关的字节码, 从而实现  "一次编写, 到处运行" 。

实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内的任何语言进行绑定, 它只与 "class文件" 这种特殊的二进制文件所关联。

2. Class类文件结构

Class文件是一组以8个字节为基础单位的二进制流, 各个数据项目严格按照顺序紧凑地排列在文件之中, 中间没有添加任何的分隔符,这使得整个Class文件中存储的内容几乎全是程序运行的必要数据,没有空隙存在。当遇到需要占用8个字节以上的数据项时,则会按照高位在前的方式分割成若干8个字节进行存储。

根据 《Java虚拟机规范》 的规定,Class文件格式采用一种类似于c语言结构体的伪结构体来存储数据, 这种伪结构体只存在两种数据类型: "无符号数" 和"表"。

  • 无符号数属于基本的数据类型, 使用u1,u2,u4,u8分别表示1,2,4,8个字节。
  • 表是由多个无符号数或者其他表为数据项构成的复合数据类型。为了便于区分, 所有的表都以 "_info"结尾。

Class文件格式

类型名称数量释义
u4magic1魔数
u2minor_version1次版本号
u2major_version1主版本号
u2constant_pool_count1常量池个数
cp_infoconstant_poolconstant_pool_count-1常量池
u2access_flags1访问标志
u2this.class1类索引
u2super.class1父类索引
u2interfaces_count1接口索引集合数量
u2interfacesinterfaces_count接口索引集合
u2fields-count1字段表集合个数
field_infofieldsfields-count字段表集合
u2methods_count1方法表集合个数
method_infomethodsmethods_count方法表集合
u2attributes_count1属性表集合个数
attribute_infoattributesattributes_count属性表集合

准备一段简单Java代码

public class TestClass1 {private int m;public int inc(){return m+1;}public static void main(String[] args) {}
}

使用jclss工具查看编译后的字节码文件

magic- 魔数

每个字节码文件的前四个字节被称之为魔数(Magic Number) ,它的唯一作用就是确定这个文件是否为一个能被虚拟机接受的Class文件。不仅是Class文件,很多文件格式标准中都有使用魔数来进行身份识别的习惯。

Class文件的魔数值为 0xCAFEBABE(咖啡宝贝)

主副版本号

紧接着魔数的4个字节存储的是Class文件的版本号,第5和第6个字节是次版本号,第7和第8是主版本号。

常量池

紧接着主次版本号之后的是常量池入口, 常量池可以比喻为Class文件里的资源仓库。

常量池中主要存放两大类常量: 字面量 和 符号引用, 字面量比较接近于java语言层面的常量概念, 入文本字符串, 被声明为final的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量:

被模块导出或开放的包(Package)

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
  • 方法句柄和方法类型
  • 动态调用点和动态常量

常量池中每一项常量都是一个表。

访问标志

在常量池结束之后,紧接着的2个字节代表访问标志,这个标志用来识别一些类或者接口层面的访问信息。

类索引,父类索引与接口索引集合

字段

当前类或接口声明的字段信息

方法

当前类或接口声明的方法信息

属性

类的属性,比如源码的文件名,内部类的列表。

3. 类加载机制

类的生命周期

一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载,验证,准备,解析,初始化,使用和卸载七个阶段,其中验证,准备,解析三部分统称为连接。

关于什么时候开始类加载过程的第一个阶段"加载" ,《Java虚拟机规范》 中并没有进行明确规定,这点根据虚拟机自行把控。但是对于初始化阶段,《Java虚拟机规范》则是严格规定了有且只有六种情况必须立刻对类进行“初始化”。

1) 遇到new, getstatic, putstatic或invokestatic 字节按指令时,下面是遇到的情况。

  • 使用new关键字实例化对象
  • 读取或者设置一个类型的静态字段(被 final修饰, 已在编译期把结果放入常量池的静态字段除外)的时候
  • 调用一个类型的静态方法的时候

2) 使用 Java.lang.reflect 包的方法对类型进行反射调用的时候, 如果类型没有进行过初始化,则需要先触发其初始化。

3) 当初始化类的时候,发现其父类还没有进行过初始化, 则需要先触发其父类的初始化。

4) 当虚拟机启动时, 用户需要指定一个要执行的类(包含main()方法的那个类), 虚拟机会先初始化这个主类。

5) 当使用jdk7 新加入的动态语言支持时

6) 当一个接口中定义了Jdk1.8 新加入的默认方法时,如果有这个接口的实现类发生了初始化,那该接口就要在其前进行初始化。

类加载过程

加载

1) 加载(Loading) 阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。

2)类加载器在加载完类之后,java虚拟机会将字节码中的信息保存到方法区中。

3)类加载完成类之后,java虚拟机会将字节码中的信息保存到方法区中。

生成一个instanceKlass对象,保存类的所有信息,里面还包含实现特定功能比如多态的信息。

4)同时,java虚拟机还会在堆中生成一份与方法区中数据类似的java.lang.Class对象。

作用是在java代码中去获取类的信息以及存储静态字段的数据(JDK8及之后)。

连接

验证

连接(Linking)阶段的第一个环节是验证,验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与。

准备

准备阶段为静态变量(static)分配内存并设置初始值。

准备阶段只会给静态变量赋初始值,而每一种基本数据类型和引用数据类型都有其初始值。

数据类型初始值
int0
byte0
short0
long0L
char'\u0000'
double0.0
booleanfalse
引用数据类型null
解析

解析阶段主要是将常量池中的符号引用替换为直接引用。

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

初始化

初始化阶段会执行静态代码块中的代码,并为静态变量赋值。

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

4. 类加载器

Java虚拟机的设计团队有意把类加载阶段中的 "通过一个类的全限定名来获取描述该类的二进制字节流" 这个动作放到Java虚拟机的外部去实现,以便让程序自己去决定如何去获取所需的类。实现这个动作的代码被称之为 "类加载器"。

类与类加载器

类加载器虽然只用于实现类的加载动作,但它在java程序中起到的作用却远远超过类加载阶段。对于任意一个类来说,都必须由加载它的类加载器和它本身共同确认其在java中的唯一性。简单来说就是完全相同的类经过不同的类加载器进行加载,那么这两个类就必定不相等。

类加载器的分类

类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。

启动类加载器 

启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟 机提供的、使用C++编写的类加载器。

默认加载Java安装目录/jre/lib下的类文件,比如rt.jar, tools.jar,resources.jar等。

扩展类加载器

扩展类加载器(Extension Class Loader)是JDK中提供的、 使用Java编写的类加载器。 默认加载Java安装目录/jre/lib/ext下的类文件。

应用程序类加载器

扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器。

它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录 或者指定jar包将字节码文件加载到内存中。

双亲委派模型

站在Java虚拟机的角度来看,只有两种不同的类加载器: 一种是启动类加载器(Bootstrap ClassLoading),这个类加载器使用C++语言实现, 是虚拟机自身的一部分。另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader

双亲委派模型的工作过程是: 如果一个类加载器收到了类加载的请求, 它首先不会自己去尝试加载这个类, 而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的类加载器的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类), 子加载器才会尝试自己去完成加载。

双亲委派模型解决什么问题?

  • 1) 重复的类: 如果一个类重复出现在三个类加载器的加载位置,那应该由谁来进行加载? 会不会出现重复?  启动类加载器加载,根据双亲委派模型,它的优先级是最高的,也不会出现重复
  • 2) 避免了系统类被覆盖的问题: 在自己项目中创建一个java.lang.String类, 会被加载吗?
  • 不能,会交由启动类加载器加载在rt.jar包中的String类
  • 3) 类加载器的关系
  • 应用类加载器的父类加载器是扩展 类加载器,扩展类加载器没有父类 加载器,但是会委派给启动类加载 器加载

双亲委派机制的作用

  • 1.保证类加载的安全性 通过双亲委派机制,让顶层的类加 载器去加载核心类,避免恶意代码 替换JDK中的核心类库,比如 java.lang.String,确保核心类 库的完整性和安全性。
  • 2.避免重复加载 双亲委派机制可以避免同一个类被 多次加载,上层的类加载器如果加 载过类,就会直接返回该类,避免 重复加载。

打破双亲委派模型

打破双亲委派机制的三种方式

打破双亲委派机制–自定义类加载器

先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法。 双亲委派机制的核心代码就位于loadClass方法中。

 正确的去实现一个自定义类加载器的方式是重写findClass方法,这样不会破坏双亲委派机制。

案例

一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类, Tomcat要保证这两个类都能加载并且它们应该是不同的类。

 如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的 MyServlet类就无法被加载了。

Tomcat使用了自定义类加载器来实现应用之间类的隔离。 每一个应用会有一个独立的类加载器加载对应的类。

打破双亲委派机制的第二种方法

JDBC案例

1、启动类加载器加载DriverManager。

 2、在初始化DriverManager时,通过SPI机制加载jar包中的myql驱动。

 3、SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。

这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制。

打破双亲委派机制的第三种方法

OSGi模块化 

历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署的 功能。 热部署指的是在服务不停止的情况下,动态地更新字节码文件到内存中。

 


总结

以上就是这篇博客的主要内容了,大家多多理解,下一篇博客见!

这篇关于JVM - 字节码文件详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1147336

相关文章

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Java中Switch Case多个条件处理方法举例

《Java中SwitchCase多个条件处理方法举例》Java中switch语句用于根据变量值执行不同代码块,适用于多个条件的处理,:本文主要介绍Java中SwitchCase多个条件处理的相... 目录前言基本语法处理多个条件示例1:合并相同代码的多个case示例2:通过字符串合并多个case进阶用法使用

Java中的Lambda表达式及其应用小结

《Java中的Lambda表达式及其应用小结》Java中的Lambda表达式是一项极具创新性的特性,它使得Java代码更加简洁和高效,尤其是在集合操作和并行处理方面,:本文主要介绍Java中的La... 目录前言1. 什么是Lambda表达式?2. Lambda表达式的基本语法例子1:最简单的Lambda表

Java中Scanner的用法示例小结

《Java中Scanner的用法示例小结》有时候我们在编写代码的时候可能会使用输入和输出,那Java也有自己的输入和输出,今天我们来探究一下,对JavaScanner用法相关知识感兴趣的朋友一起看看吧... 目录前言一 输出二 输入Scanner的使用多组输入三 综合练习:猜数字游戏猜数字前言有时候我们在

Spring Security+JWT如何实现前后端分离权限控制

《SpringSecurity+JWT如何实现前后端分离权限控制》本篇将手把手教你用SpringSecurity+JWT搭建一套完整的登录认证与权限控制体系,具有很好的参考价值,希望对大家... 目录Spring Security+JWT实现前后端分离权限控制实战一、为什么要用 JWT?二、JWT 基本结构

java解析jwt中的payload的用法

《java解析jwt中的payload的用法》:本文主要介绍java解析jwt中的payload的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java解析jwt中的payload1. 使用 jjwt 库步骤 1:添加依赖步骤 2:解析 JWT2. 使用 N

springboot项目如何开启https服务

《springboot项目如何开启https服务》:本文主要介绍springboot项目如何开启https服务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录springboot项目开启https服务1. 生成SSL证书密钥库使用keytool生成自签名证书将

Java实现优雅日期处理的方案详解

《Java实现优雅日期处理的方案详解》在我们的日常工作中,需要经常处理各种格式,各种类似的的日期或者时间,下面我们就来看看如何使用java处理这样的日期问题吧,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言一、日期的坑1.1 日期格式化陷阱1.2 时区转换二、优雅方案的进阶之路2.1 线程安全重构2

Java中的JSONObject详解

《Java中的JSONObject详解》:本文主要介绍Java中的JSONObject详解,需要的朋友可以参考下... Java中的jsONObject详解一、引言在Java开发中,处理JSON数据是一种常见的需求。JSONObject是处理JSON对象的一个非常有用的类,它提供了一系列的API来操作J

SpringBoot多数据源配置完整指南

《SpringBoot多数据源配置完整指南》在复杂的企业应用中,经常需要连接多个数据库,SpringBoot提供了灵活的多数据源配置方式,以下是详细的实现方案,需要的朋友可以参考下... 目录一、基础多数据源配置1. 添加依赖2. 配置多个数据源3. 配置数据源Bean二、JPA多数据源配置1. 配置主数据