Class文件结构全面解析(上)

什么是Class文件? 在Java刚刚诞生的时候就提出了一个非常著名的口号:“一次编写,到处运行。(Write Once,Run Anywhere)”。为了实现平台无关性,各种不同平台的虚拟机都统一使用一种程序储存格式,就是字节码(ByteCode)。它就以二进制字节流的方式被存放在Class文件中,其中包含了Java虚拟机指令集和符号表以及其他辅助信息。

为什么需要了解Class文件结构? 一般对于数据结构的分享难免比较枯燥,但是了解Class文件结构是了解Java虚拟机的重要基础之一。如果想比较深入地了解Java虚拟机,那么Class文件结构是不能不接触的。我会力求在保证逻辑准确的基础上,尽量通俗易懂地分享,并结合实际案例。

Class文件结构简介 Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序准确地排列在Class文件中,中间没有任何分隔符。当遇到8位字节以上的数据时,就按照高位在前的方式(最高位字节在地址最低位、最低位字节在地址最高位的顺序储存)分割成多个8位字节储存。

Class文件格式采用一种类似于C语言结构体的伪结构来储存数据的,这种伪结构有两种数据类型:无符号数和表。

无符号数用u1、u2、u4、u8分别代表1个字节、2个字节、4个字节和8个字节的无符号数,可以用来描述数字、索引引用、数量值或者UTF-8编码构成的字符串值。

表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有的表都习惯地以“_info”结尾。表的数据结构和树很类似,无符号数相当于它的叶子节点,其他的表相当于它的子节点。整个Class文件就本质上也是一个表,具体结构如下:

image

可以发现,无论是无符号数还是表,当需要描述同一种类型又数量不定的多条数据时,就会用一个前置的计数器加几个连续的数据项的方式,这个时候我们就把这种一系列连续的某种类型的数据叫做这个类型的集合。

在Class文件中,无论是顺序还是数量,甚至是数据存储的字节序,都必须严格按照上面表格进行设定,哪个字节代表什么含义,长度是多少,先后顺序怎么样,都不允许改变。接下来看一下各个数据项的具体含义。

魔数

魔数(Magic Number)是每个Class文件的前4个字节,它用来确定当前文件是否是一个被Java虚拟机所接受的Class文件。很多文件存储标准中都使用了魔数进行身份识别,比如gif、jpeg等图片文件中都有魔数。使用魔数而不使用扩展名是出于安全考虑,因为扩展名更容易被修改。文件格式制定者可以自主选择魔数,只要这个魔数没有被广泛使用又不和其他文件混淆就可以。

Class文件的魔数是:0xCAFEBABE,这个魔数在Java还被称为“Oak”语言的时候就确定下来了,据Java开发小组最初的关键成员Patrick Naughton说:“我们一直在寻找一些好玩的、容易记忆的东西,选择0xCAFEBABE是因为它象征着著名咖啡品牌Peet’s Coffee中深受欢迎的Baristas咖啡”,他们是真的很喜欢喝咖啡啊,可能也预示着日后“Java”这个名字的出现。

为了更快的理解,我准备了一个实际案例,一段非常简单的Java代码:

public class OneMoreStudy {
    private int number;

    private int plusOne() {
        return number + 1;
    }
}

使用JDK 1.7把这段代码编译成Class文件,用HexEd打开,就可以到魔数了,如下图:

image

在接下来的分享中,也会经常使用这个Class文件。

次版本号和主版本号 紧跟着魔数的第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。Java的主版本号是从45开始的,从JDK 1.1以后每个JDK大版本发布主版本号都加1,高版本的JDK向下兼容低版本的Class文件,但不能运行更高版本的Class文件,即使Class文件的格式没有发生任何变化,Java虚拟机也会拒绝运行超过其版本号的Class文件。

再来看一下之前的Class文件例子:

image

表示次版本号的第5和第6个字节值为0x0000,表示主版本号的第7和第8个字节值为0x0033,也就是十进制的51,说明这个Class文件可以被JDK 1.7及其以上版本的Java虚拟机运行。

常量池 紧跟着主版本号的就是常量池,它可以理解为Class文件的资源仓库,也是Class文件结构中与其他数据项关联最多的数据类型。因为在常量池中的常量数量是不固定的,所以首先有一个u2类型的数据,表示常量池容量大小(constant_pool_count)。

常量池的容量计数不是从0开始的,而是从1开始的,这是因为0有它的特殊用用途,那就是为了表达在特殊情况下需要表达“不引用任何一个常量池项目”的含义。在Class文件结构中只有常量池的容量计数是从1开始的,对于其他集合,包括接口索引集合、字段集合、方法集合等的容量计数都是从0开始的。

再来看一下之前的Class文件例子:

image

常量池容器计数值为0x0013,也就是十进制的19,它表示常量池中有18个常量,索引值范围从1到18。

常量池中主要存储两种常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近Java语言层面的常量,比如文本字符串、声明为final的常量值。符号引用则是编译原理层次的概念,它包括以下三种:

类和接口的全限定名

字段的名称和描述符

方法的名称和描述符

常量池中每一个常量都是一个表,共有14种不同的常量类型(JDK1.7及之前版本),每一种类型的表在第一位都有一个u1类型的标志位,具体如下表:

image

有个一个专门分析Class文件字节码的工具javap,我们用它直接看一下之前的Class文件例子里的18个常量(常量池以外的信息已省略):

E:\>javap -verbose OneMoreStudy
  Compiled from "OneMoreStudy.java"
  minor version: 0
  major version: 51
Constant pool:
   #1 = Methodref     #4.#15     // java/lang/Object."<init>":()V
   #2 = Fieldref      #3.#16     // OneMoreStudy.number:I
   #3 = Class         #17        // OneMoreStudy
   #4 = Class         #18        // java/lang/Object
   #5 = Utf8          number
   #6 = Utf8          I
   #7 = Utf8          <init>
   #8 = Utf8          ()V
   #9 = Utf8          Code
  #10 = Utf8          LineNumberTable
  #11 = Utf8          plusOne
  #12 = Utf8          ()I
  #13 = Utf8          SourceFile
  #14 = Utf8          OneMoreStudy.java
  #15 = NameAndType   #7:#8       // "<init>":()V
  #16 = NameAndType   #5:#6       // number:I
  #17 = Utf8          OneMoreStudy
  #18 = Utf8          java/lang/Object

其中,有一些常量好像在代码里没有出现过,如“I”、“”、“Code”、“LineNumberTable”、“SourceFile”。它们其实自动生成的,是后面要分享的字段表、方法表、属性表引用到的,用于描述一些不方便使用“固定字节”进行表达的内容。

访问标志 紧跟着常量池的2个字节表示访问标志(access_flags),它用于识别一些类或接口层次的访问信息,具体见下表:

image

其中,ACC_SUPER在JDK 1.0.2之后编译出来的Class文件必须为true;ACC_ABSTRACT对于接口或抽象类来说为true,其他类为false。

之前的例子OneMoreStudy是一个普通的类,不是接口、注解或枚举,只被public修饰,没有被声明为final或abstract,而且是JDK 1.7编译的,所以只有ACC_PUBLIC和ACC_SUPER为true,所以它的访问标志应该是0x0001 | 0x0020 = 0x0021,如下图:

image

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值