还不懂类文件结构?看看这篇文章

值得强调的是,Java虚拟机并不是Java语言所独自占有的,虚拟机不关心来源是何种语言。只要输入规范的统一的程序存储格式——字节码文件,就能产生需要的效果

Class类文件结构

首先把干货罗列出来

Class类文件是一种“高效率”的结构

Class文件是一组以8位字节为基础单位的二进制流,数据项目严格按照顺序紧密排列,Class文件中几乎所有内容都是程序运行必要的数据,没有空隙

Class文件按照顺序,大致分为

  • 魔数(4字节)
  • 版本号(5,6字节)
  • 常量池-访问标志(access_flags)
  • 类索引、父索引、接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

Class文件采用类似C语言结构体的伪结构只存储无符号数据,或无符号数据组成的集合——

  • 无符号数可以描述数字引用数量值按照UTF-8编码构成的字符串值,u1,u2,u3,u8分别代表1,2,4,8个字节的无符号数
  • 多个无符号数或者其他表构成的一种复合数据类型,以“_info”结尾,Class文件为一张表
类型名称中文名称数量
u4magic魔数1
u2minor_version次版本号1
u2major_version主版本号1
u2constant_pool_count常量池容量计数值1
cp_infoconstant_pool常量池constant_pool_count-1
u2access_flags访问标志1
u2this_class类索引1
u2super_class父类索引1
u2interfaces_count接口数量1
u2interfaces接口interface_count
u2fields_count字段数量1
field_infofields字段fields_count
u2methods_count方法数量1
method_infomethods方法methods_count
u2attributes_count属性数量1
attribute_infoattributes属性attributes_count

到这里,很容易迷茫,这里我作出一张更为详细的表格方便理解

在阅读这个表格前,希望各位回忆一下Java对象的基本组成

public class MyClass extends Father implements Boy,Girl{
    private String name;
    public int age;
    public void speak(){}
}

Class文件是高效率存储程序信息的文件,一字不多、一字不少的存储了一个类

从下面的表中,你可以清晰的看到类的各个部分。

请先对该部分有一个印象,再继续阅读。

类文件结构

Class类文件的结构就是这样,下面详细的说说各个部分。

个人感觉后面的内容比较枯燥,使用生动的例子反而不是很清晰,若只是希望有一个了解,阅读至此,已经足以,若要对各个项目详细窥探,ways are yours

1、魔数与Class文件版本

打开你的Class文件,会发现开头为cafe,这就是魔数,0xCAFEBABE,这是Class文件的标识符

紧跟魔数的4个字节,为Class文件的版本号

JDK不能执行高于本版本的字节码文件,即使文件未变化,虚拟机也必须拒绝执行超过其版本号的Class文件。

2、常量池

位置:主次版本号之后。

Class文件中的资源仓库,在Class文件中与其他项目关联最多(其他项目需要常量才能正确表达),占用了Class文件空间最大的数据项目之一

  • 表类型数据项目
  • 入口为一个计数值,从1开始而不是从0开始
  • 当某个项目“不引用任何一个常量池项目”时,指向0

主要存放

  • 字面量:接近常量,文本字符串、声明为final的常量值等等。
  • 符号引用:各种
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

Java在编译时没有“连接”的步骤,Java通过在虚拟机加载Class文件时进行动态连接。Class文件中不会保存各个方法、字段的内存布局信息,不经过运行期转换的话无法得到真正的内存入口地址,无法被虚拟机使用。笔者认为这是Java的方法脱离了类,就不能使用的来源。

常量池中的每一项都是表类型(复合类型)

其中的项目开头第一位是一个u1类型(4位无符号数)的标志位(tag,取值如下)代表当前的常量类型。(JDK1.7前为11种,1.7后增加了三种)

类型标志描述
CONSTANT_Utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符合或引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18标识一个动态方法调用点

其中CONSTANT_Utf8_info类型

类型名称数量
u1tag1
u2length1
u1byteslength

由于Class文件中方法、字段等都引用CONSTANT_Utf8_info型常量来描述,所以CONSTANT_Utf8_info的最大长度就是Java中方法、字段名的最大长度。即length的最大值,u2类型的最大值65535。所以超过64KB英文字符的变量或方法名,将无法编译。

3、访问标志

位置:常量池后紧接的两个字节

用于识别一些类或者接口层次的访问信息。

  • 该Class类还是接口
  • 是否定义为public
  • 是否定义为abstract类型
  • 是类的情况下,是否被定义为final

可以使用16个标志位,目前使用了8个

4、类索引、父类索引与接口索引集合

类索引和父类索引为u2类型,接口为一组u2类型数据的集合

这三项数据确定了该类的继承关系。

Object类的父类索引为0.

接口集合包括一个接口计数器,后接接口值的索引(真实值存储在常量池)

5、字段表集合

用于描述声明的变量。

字段表的结构为访问标志、名称的索引、参数信息构成的集合,不在详细的描述

值得一提的是,字段在类文件中,以简单名称进行储存(与全限定名相对,另外全限定名在存储时,点替换为斜杠)

虚拟机提供了描述标识符类型的标识字符

若字段是一个数组类型,在每一个维度前将放置一个“[”进行标识

如java.lang.Stringp[][]将记录为“[[Ljava/lang/String;”

另外,Java中,字段的名字不能重复,而对于字节码,若名字相同,类型描述符不同,则是合法的

6、方法表集合

方法表与字段表极为类似,

不过需要注意的是:方法表中只记录了方法的定义信息,而方法体存放在属性表中一个名为“Code”的属性中。

在Java中,重载(Overload,就是名字相同的那种)一个方法,除了相同的简单名称,还要求必须拥有一个与原方法不同的特征签名(Java中为方法名称、参数顺序、参数类型)。

因为在字节码文件中,特征签名多了方法的返回值以及受查异常表,所以在字节码中,重载的范围更宽了一些。

7、属性表集合

属性表的限制稍微宽松,不再严格要求各个项目的顺序,并且只要不与已知属性名重复,都可以写入自己定义的属性信息,Java虚拟机运行时会忽略不认识的属性。

下面给出预定义的一些属性表

在这里插入图片描述

在这里插入图片描述

其中,我们大致提出一些主要的属性

Code属性

Java程序的方法体经过Javac编译器处理后,最终转换为字节码存储在Code属性中,Code属性出现在方法表中的属性集合之中,但并非所有的方法表都必须有这个属性,比如接口和抽象类中的方法,就不存在Code属性,如果方法表有Code属性存在,那么他的结构如下
img
其中,

  • 第一个u2 attriibute_name_index,指向了常量池中的一个utf8类型常量的索引,此常量固定值为Code
  • attribute_length表示了属性值的长度。
  • max_stack代表了操作数栈深度的最大值,在方法执行的任意时刻,操作数栈都不会超过这个深度,虚拟机在运行时根据这个值来分配栈帧中的操作树栈深度。
  • max_locals代表了局部变量表所需的存储空间。
    • 在这里,max_locals的单位是槽(slot),变量槽是虚拟机为局部变量分配内存的最小单位,对于byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个变量槽,而double和long这种64位的数据类型则需要两个变量槽
    • 方法参数(包括实例方法中隐含的this)、显示异常处理程序的参数(在catch中定义的异常值)、方法体中定义的局部变量都依赖局部变量表来存放。
    • 但是要注意,并不是方法中有多少个局部变量,就把这些局部变量表所占空间之和作为max_locals的值,操作数栈和局部变量表决定了一个方法的栈帧大小,不必要的数量会导致内存的浪费。
    • Java虚拟机的做法是将局部变量表的局部变量池进行重用,当代码执行超过一个局部变量的作用域时,这个局部变量所占用的局部变量槽可以被其他局部变量使用,javac编译器会根据变量的作用域来分配变量槽来给各个变量使用,然后根据同时生存的最大局部变量数和类型来计算出max_locals的大小。
  • code_length和code用来存储Java源程序编译后产生的字节码指令,code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。虽然code_length是一个u4类型的长度值,也就是理论上可以达到2的32次幂,但是根据java虚拟机规范,一个方法不允许超过65535条字节码指令,即实际值使用到了u2的长度。

在字节码指令之后的是显式异常处理表,异常表对于Code属性来说并不是必须存在的。其结构如图
img
字段的含义为,当字节码从start_pc行到end_pc行之间出现了类型为catch_type或者其子类的异常时,则转到第handler_pc行继续处理。 当catch_type的值为0时,代表任意异常情况都需要转到handler_pc行进行处理。

Exceptions属性

这个Exceptions属性是方法表中与Code属性同级的一项属性。作用是列举一个方法可能抛出的受检查异常(Checked Exceptions),也就是方法描述时,throws关键字后面列举的异常,其结构如下图
img
其中exception_index_table指向了常量池中CONSTANT_Class_info的索引。

LineNumberTable属性

LineNumberTable属性是用于描述Java源代码行号和字节码行号之间关系的。他不是运行时必须的,可以通过javac的参数 -g参数进行取消或生成。 如果取消这个属性,最大的影响就是在运行时抛出的异常不会包含报错行号,在调试程序时也无法根据源码行号来设置断点。

LocalVariableTable和LocalVariableTypeTable属性

LocalVariableTable属性是用来描述栈帧中局部变量表的变量与java源码中定义的变量之间的关系,也不是运行时必须的属性,如果不生成,最大的影响是对IDE工具调试时无法根据参数名从上下文中获取参数值。

在JDK5引入泛型后,LocalVariableTable属性增加了一个姐妹属性,LocalVariableTypeTable,其结构与LocalVariableTable很相似,仅仅是把描述字段的字段描述符替换成了字段的特征签名。由于描述符中泛型参数化类型被擦除,描述符不能准确描述泛型类型了,因此出现了LocalVariableTypeTable属性,使用特征签名来完成泛型的描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重中之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习。课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值