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

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof