【JVM】Class文件结构详解
1 简介
Class文件是一组以字节为基础单位的二进制流。
JVM的数据入口就是我们平时代码编译后的Class文件,那么要搞懂JVM的原理之前,就要了解Class文件里边都是什么,才能更好的理解JVM原理。
内容比较多,希望看到的小伙伴也能自己动手,边看边解析一个自己的Class文件,光看不操作其实等于白看,因为过两天你就忘了。
2 准备工作
一个Class文件 、NotePad++文本文件查看
Class文件,用文本工具打开,是一堆乱码,因为它本身是二进制流的。
用NotePad++的插件有个HEX插件,就是以16进制的方式显示。
javap -verbose .class 反编译看看Class文件信息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 kuku@DESKTOP-KQS9B3J MINGW64 ~/Desktop 2 $ javap -verbose BscDictService.class 3 Classfile /C:/Users/kuku/Desktop/BscDictService.class 4 Last modified 2022-6-9; size 1727 bytes 5 MD5 checksum 87ad0a7a75e3755bdbb266ee5e77a998 6 Compiled from "BscDictService.java" 7 public interface com.goldjet.appservice.customs.application.bsc.BscDictService extends com.bitsun.core.framwork.application.IApplicationService 8 minor version: 0 9 major version: 52 10 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT 11 Constant pool: 12 #1 = Class #31 // com/goldjet/appservice/customs/application/bsc/BscDictService 13 #2 = Class #32 // java/lang/Object 14 #3 = Class #33 // com/bitsun/core/framwork/application/IApplicationService 15 #4 = Utf8 save 16 #5 = Utf8 (Lcom/goldjet/appservice/customs/dto/req/bcs/BscDictReqDto;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 17 #6 = Utf8 MethodParameters 18 #7 = Utf8 reqDto 19 #8 = Utf8 deleteByIds 20 #9 = Utf8 (Ljava/lang/String;)Ljava/lang/Void; 21 #10 = Utf8 ids 22 #11 = Utf8 doPager 23 #12 = Utf8 (Ljava/util/Map;)Lcom/bitsun/core/common/persistence/Pager; 24 #13 = Utf8 params 25 #14 = Utf8 Signature 26 #15 = Utf8 (Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Lcom/bitsun/core/common/persistence/Pager<Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto;>; 27 #16 = Utf8 selectOne 28 #17 = Utf8 (Ljava/lang/Long;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 29 #18 = Utf8 id 30 #19 = Utf8 (Ljava/util/Map;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 31 #20 = Utf8 (Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 32 #21 = Utf8 updateProps 33 #22 = Utf8 (Ljava/lang/Long;Ljava/util/Map;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 34 #23 = Utf8 (Ljava/lang/Long;Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 35 #24 = Utf8 (Ljava/lang/Long;Lcom/goldjet/appservice/customs/dto/req/bcs/BscDictReqDto;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 36 #25 = Utf8 selectDictItemByDictCode 37 #26 = Utf8 (Ljava/lang/String;)Ljava/util/List; 38 #27 = Utf8 dictCode 39 #28 = Utf8 (Ljava/lang/String;)Ljava/util/List<Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictItemResDto;>; 40 #29 = Utf8 SourceFile 41 #30 = Utf8 BscDictService.java 42 #31 = Utf8 com/goldjet/appservice/customs/application/bsc/BscDictService 43 #32 = Utf8 java/lang/Object 44 #33 = Utf8 com/bitsun/core/framwork/application/IApplicationService 45 { 46 public abstract com.goldjet.appservice.customs.dto.res.bcs.BscDictResDto save(com.goldjet.appservice.customs.dto.req.bcs.BscDictReqDto); 47 descriptor: (Lcom/goldjet/appservice/customs/dto/req/bcs/BscDictReqDto;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 48 flags: ACC_PUBLIC, ACC_ABSTRACT 49 MethodParameters: 50 Name Flags 51 reqDto 52 53 public abstract java.lang.Void deleteByIds(java.lang.String); 54 descriptor: (Ljava/lang/String;)Ljava/lang/Void; 55 flags: ACC_PUBLIC, ACC_ABSTRACT 56 MethodParameters: 57 Name Flags 58 ids 59 60 public abstract com.bitsun.core.common.persistence.Pager<com.goldjet.appservice.customs.dto.res.bcs.BscDictResDto> doPager(java.util.Map<java.lang.String, java.lang.Object>); 61 descriptor: (Ljava/util/Map;)Lcom/bitsun/core/common/persistence/Pager; 62 flags: ACC_PUBLIC, ACC_ABSTRACT 63 MethodParameters: 64 Name Flags 65 params 66 Signature: #15 // (Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Lcom/bitsun/core/common/persistence/Pager<Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto;>; 67 68 public abstract com.goldjet.appservice.customs.dto.res.bcs.BscDictResDto selectOne(java.lang.Long); 69 descriptor: (Ljava/lang/Long;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 70 flags: ACC_PUBLIC, ACC_ABSTRACT 71 MethodParameters: 72 Name Flags 73 id 74 75 public abstract com.goldjet.appservice.customs.dto.res.bcs.BscDictResDto selectOne(java.util.Map<java.lang.String, java.lang.Object>); 76 descriptor: (Ljava/util/Map;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 77 flags: ACC_PUBLIC, ACC_ABSTRACT 78 MethodParameters: 79 Name Flags 80 params 81 Signature: #20 // (Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 82 83 public abstract com.goldjet.appservice.customs.dto.res.bcs.BscDictResDto updateProps(java.lang.Long, java.util.Map<java.lang.String, java.lang.Object>); 84 descriptor: (Ljava/lang/Long;Ljava/util/Map;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 85 flags: ACC_PUBLIC, ACC_ABSTRACT 86 MethodParameters: 87 Name Flags 88 id 89 params 90 Signature: #23 // (Ljava/lang/Long;Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 91 92 public abstract com.goldjet.appservice.customs.dto.res.bcs.BscDictResDto updateProps(java.lang.Long, com.goldjet.appservice.customs.dto.req.bcs.BscDictReqDto); 93 descriptor: (Ljava/lang/Long;Lcom/goldjet/appservice/customs/dto/req/bcs/BscDictReqDto;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto; 94 flags: ACC_PUBLIC, ACC_ABSTRACT 95 MethodParameters: 96 Name Flags 97 id 98 reqDto 99 100 public abstract java.util.List<com.goldjet.appservice.customs.dto.res.bcs.BscDictItemResDto> selectDictItemByDictCode(java.lang.String); 101 descriptor: (Ljava/lang/String;)Ljava/util/List; 102 flags: ACC_PUBLIC, ACC_ABSTRACT 103 MethodParameters: 104 Name Flags 105 dictCode 106 Signature: #28 // (Ljava/lang/String;)Ljava/util/List<Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictItemResDto;>; 107 } 108 SourceFile: "BscDictService.java"
3 文件结构
Class文件是一组以8位字节为基础单位的二进制流,内容是以《Java虚拟机规范》规定的一种固定的格式存储数据。存储数据用到了两个数据类型:“无符号数”和“表”。
无符号数:无符号数是基本数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量表或者按照UTF-8编码构成字符串值。
表:由多个无符号数或者其他表作为数据项构成的复合数据类型,以“_info”结尾,用户描述有层次关系的复合结构的数据。
下图是《Java虚拟机规范》规定的Class文件的固定格式,所有Class文件均按照一下格式存储内容。(注意:每个class文件内容都是按照以下列表按列表中从上到下顺序组成,如果某些类型不涉及可以为空)
4 文件结构分析
4.1 魔数
魔数,固定死的一个前缀,可以理解为这个魔数就是表示这是一个Class文件。
4.2 版本号(次版本号 主版本号)
紧接着的是版本号的信息,先是次版本号,后是主版本号。
主版本号0034(16进制)转为十进制为52, 52表示多少,可以根据Class文件版本号对照表,得知是1.8,高版本的JDK可以向下兼容以前版本的Class文件,但是无法运行以后版本的Class文件,即使文件格式并未发生变化,虚拟机也必须拒绝执行超过其版本号的Class文件。java虚拟机向上不兼容就是在虚拟机加载类文件时文件校验的这里。
4.3 常量池
什么是常量池?就是你的java源码中的类名字啊、字段名啊、方法名啊、返回值类型啊、变量类型啊等等。
常量池中主要存放两大类常量:
1.字面量:比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
2.符号引用:属于编译原理方面的概念,包括被模块导出或者开放的包(Package)、类和接口的权限定名、字段和方法的名称和描述符等
Class文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用的。当虚拟机做类的加载时,将会从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
因为常量池中的第0个位置被我们的jvm占用了表示为null,所以我们通过编译出来的常量池索引是从1开始的。
常量池长度后边紧接着的是每一个常量,每一项常量都是一个表,截止到JDK13《Java虚拟机规范》规定的常量类型有17种结构各不相同表结构数据,下表列出的是11种常见的常量池常量类型表:
查表后,发现这11类表都有一个共同的特点,表结构起始的第一位都是个u1类型的标志位(tag,取表中描述列的值),下面就我们的demo来分析一个常量类型:
第1个常量:( 07 00 1F) 07对应的值为7 紧接着两个字节 00 1F表示它指向的是常量池的索引是31的常量
跟我们反编译以后的是不是对应起来了1号常量 #31指向了索引是31的常量
第2个常量:(07 00 20) 07对应的值为7 紧接着两个字节 00 20表示它指向的是常量池的索引是32的常量
第3个常量:(07 00 21) 07对应的值为7 紧接着两个字节 00 21表示它指向的是常量池的索引是33的常量
第4个常量:(01 00 04 73 61 76 65) 01对应的值是1,可以看到紧接着的两个字节表示该常量占用的字节数 0004=4即4个字节,再取4个字节出来即73 61 76 65即save
第5个常量:字节比较多,就不写了看图 01即又是值1的 0076=118 表示要取118个字节出来即(Lcom/goldjet/appservice/customs/dto/req/bcs/BscDictReqDto;)Lcom/goldjet/appservice/customs/dto/res/bcs/BscDictResDto;
第6个常量-到第18个常量:想必你应该也熟练了我就快速标了哈
第19个常量-到第23个常量:想必你应该也熟练了我就快速标了哈
第24个常量-到第33个常量:想必你应该也熟练了我就快速标了哈
这样常量池这一块就结束了,解释一下L开头的或者[、[[开头的都表示什么意思,基本参数类型和void类型都是用一个大写的字符来表示,对象类型是通过一个大写L加全类 名表示,这么做的好处就是在保证jvm能读懂class文件的情况下尽量的压缩class文件体积
基本数据类型表示:
B---->byte, C---->char, D---->double, F----->float,I------>int
J------>long, S------>short, Z------>boolean, V------->void
对象类型:
比如String------>Ljava/lang/String;(后面有一个分号)
数组类型:
每一个唯独都是用一个前置 [ 来表示 比如: int[] ------>[ I String [][]------>[[Ljava.lang.String;
用描述符来描述方法的,先参数列表,后返回值的格式,参数列表按照严格的顺序放在()中 比如源码 String getUserInfoByIdAndName(int id,String name) 的方法描述符号 (I,Ljava/lang/String;)Ljava/lang/String;
4.4 访问标志
紧接着常量池后面的就是当前类或者接口的访问标志(access_flags),解析我们的class文件是类还是接口,是否定义为public的,是否是abstract,是否被 final修饰。
访问标志:06 01 ,ACC_PUBLIC | ACC_INTERFACE | ACC_ABSTRACT = 0001 | 0200 | 0400 = 0601 (进行的是或运算)
4.5 类索引、父类索引和接口集合
紧接着访问标志后面的就是类索引、父类索引、实现接口,类索引其实就是类名字指向的常量池的索引,父类索引也是指向常量池的索引。
00 01 类索引 00 02父类索引 00 01 实现接口数量 (表示实现了一个接口)00 03 实现的接口是第3个常量池的索引
4.6 字段表信息
紧接着接口结构表后面的就是字段表集合,作用:用于描述类和接口中声明的变量,包括类变量和实例变量,但是不包括方法的局部变量。
field_count好理解就是类中声明了多少个变量,值就是多少;那表结构field_info到底是什么样子的,都有什么属性来修饰变量呢,来看下面的字段表结构:
00 00 我没有字段,尴尬。
给你们找一个别人的有字段的解析哈,
4.6 方法表集合
紧接着字段表后面的就是方发表,和字段表集合一样,计数值后面接一个方法结构表。
方法表结构和字段表结构一模一样,见下表:依次是访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项
这里方法的访问标志和类、字段的不一样,如下:
00 08 表示接口中有8个方法
第1个方法:04 01 00 04 00 05 00 01 这是1个方法的访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表(属性长度、属性结构表),对于属性表我们会在下边的属性表集合说。
4.6 属性表集合
紧接着每个字段表的每个方法的后面的就是属性表,同时我们在字段表和方法表集合中都携带自己的属性表集合,属性表集合也是u2类型的计数值和结构表组成,如下:
第一个方法后边的属性的分析:
我的Class文件是一个接口编译后的,方法表和属性跟类的还不一样,我在这里,先把这个接口的Class文件分析完,分析一个简单类的方法和属性,剩余方法的分析:
每个方法中间,就是方法的属性表,最后接口的这个Class文件 最后剩余10个字节,不知道是干啥的,回头我再研究。
Class文件基本如上,我们再拿一个类的Class文件分析下,方法表和属性表这里:
javap后的信息:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
$ javap -verbose Demo.class Classfile /D:/JetBrains/yanjiu/virtuous-demo/src/main/java/com/virtuous/demo/laboratory/sort/Demo.class Last modified 2023-2-10; size 353 bytes MD5 checksum f7f5e91d7b6282fa9e9a60561cf72bcb Compiled from "Demo.java" public class com.virtuous.demo.laboratory.sort.Demo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #4.#15 // java/lang/Object."<init>":()V #2 = String #16 // 111 #3 = Class #17 // com/virtuous/demo/laboratory/sort/Demo #4 = Class #18 // java/lang/Object #5 = Utf8 name #6 = Utf8 Ljava/lang/String; #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 getName #12 = Utf8 (Ljava/lang/String;)Ljava/lang/String; #13 = Utf8 SourceFile #14 = Utf8 Demo.java #15 = NameAndType #7:#8 // "<init>":()V #16 = Utf8 111 #17 = Utf8 com/virtuous/demo/laboratory/sort/Demo #18 = Utf8 java/lang/Object { public com.virtuous.demo.laboratory.sort.Demo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 7: 0 public java.lang.String getName(java.lang.String); descriptor: (Ljava/lang/String;)Ljava/lang/String; flags: ACC_PUBLIC Code: stack=1, locals=2, args_size=2 0: ldc #2 // String 111 2: areturn LineNumberTable: line 12: 0 } SourceFile: "Demo.java"
IDEA下载一个插件jclasslib,可以看你的Class文件信息
针对这个类的Class文件我们分析下他的属性、方法表和属性表:
由属性名字索引指向常量池第9个字符串常量,我们可知该属性为“Code”属性,虚拟机规范规定code属性,使用位置在方法表,含义是java代码编译成的字节码指令 Java程序方法体内里面的代码经过javac编译器处理后,最终变为字节码指令存储在Code属性内。属性长度为29,那么这29个u1字节什么含义呢,下面我们来看看Code属性表结构,如下表:
Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码(Code即方法体里面的java代码)和元数据(MetaData,包括类、字段、方法定义及其他信息)两部分,那么在整个Class文件里,Code属性用于描述代码,所有的其他数据项目都用于描述元数据。
对Demo的两个方法进行详细拆分:
第1个构造方法:
第2个getName方法:
start_pc:这个局部变量的生命周期开始的字节码偏移量 占用二个字节
length:作用范围覆盖的长度 占用二个字节
name_index:表示局部变量的名称 二个字节
desc_index:表示局部变量描述符索引 二个字节
index:index是这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类 型是64位类型时(double和long),它占用的Slot为index和index+1两个
4.7 Class文件属性
最后10个字节,我知道表示啥了,表示Class文件最后的属性即SourceFile Demo.java
累死我了,写一天了。