JVM-Class文件
一个 Class 文件描述了类或接口的字段,方法,父类,访问权限等全部信息。其实,它只是一种能被 JVM 识别的数据格式,就和 UDP 8字节头部一样,这就是规范,标准!所谓“不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之,学至于行而止矣,行之,明也”。本文最后将动手分析一个 Class 文件的字节和反编译后的伪汇编语言,来探讨其结构。
本文将介绍:
- Class 头部信息
- Class 常量池信息
- Class 父类,访问权限和接口信息
- Class 字段及其属性
- Class 方法及其属性
- 其他属性和实例描述
Class 文件是以 8-bit 为单位的字节流,并且多字节数据以 big-endian 排列(又称为网络序,高位在前,符合我们的直觉,如日常使用的十进制)。注意使用 javac –g 编译源码,-g 表示生成所有的调试信息,javap –p –v 显示全部的伪汇编语言。根据 JVM 规范,ClassFile 格式如下:

1. Class 头部信息
对于定长的字段,比较好解析,麻烦的是变长的字段,需要根据不同类型,提取不同长度的字节进行解析,比如常量池中的信息,不同字段的结构解析方式不同。前三个字段还是比较好解析的,前4个字节为魔数 magic=0xcafebabe,2字节的从版本号 minor=0x0000,2字节的主版本号 major=0x0034=3*16+4=52,如下:

2. Class 常量池信息
JVM 执行时,不依赖类,实例或接口在内存是怎么布局的,而是依赖运行时常量池,基于栈来执行指令。常量池中包含字面量和符号引用,字面量就是字符串常量和数值常量;符号引用就是用来描述类或接口、字段名和其描述符、方法名和其描述符。根据 ClassFile 的结构,可以看到 2 字节表示常量池大小,后面 cp_info 是具体内容,而 cp_info不同类型的大小不同,常量池中的类型如下:
| 类型 | 标志 | 大小(字节) | 描述 |
| CONSTANT_Utf8_info | 0x01 | 3+len | 字符串,数值常量,类全限定名,方法名,字段名,属性名等字面量 |
| CONSTANT_String_info | 0x08 | 3 | 字符串类型符号引用,引用 utf8 表示的字面量 |
| CONSTANT_Class | 0x07 | 3 | 类或接口的符号引用 |
| CONSTANT_NameAndType | 0x0c | 5 | 字段或方法的符号引用,但没有指明是哪个类或接口的方法 |
| CONSTANT_Fieldref | 0x09 | 5 | 字段的符号引用,表明所属类,和字段名 |
| CONSTANT_Methodref | 0x0a | 5 | 方法的符号引用,表明所属类,和方法名 |
| CONSTANT_InterfaceMethodref | 0x0b | 5 | 接口中方法的符号引用,表明所属接口,和方法名 |
| CONSTANT_Integer | 0x03 | 5 | 整型字面量 |
| CONSTANT_Float | 0x04 | 5 | 浮点型字面量 |
| CONSTANT_Long | 0x05 | 6 | 长整型字面量 |
| CONSTANT_Double | 0x06 | 6 | 双精度浮点型字面量 |
| CONSTANT_MethodHandle | 0x0f | 4 | 表示方法句柄 |
| CONSTANT_MethodType | 0x10 | 3 | 表示方法类型 |
| CONSTANT_InvokeDynamic | 0x12 | 5 | 动态调用 |
为了方便阅读,上表没有给出具体的结构,可查看 JVM规范 (§4)。知道了常量池中类型的大小,就可以进行解析了,下图是常量池的部分字节:

其中,连续的,颜色相同的框表示一个类型的信息。常量池元素总数为【00 22】= 34个,JVM 会对常量池中解析的类型从 1 进行编号,比如【0a 00 06 00 14】标识为10,查询上表,可知此字段为方法的符号引用,大小为5字节,其内容为【#1 = Methodref #6.#20】 后面引用所属类和方法描述。其他字段也是如此,如果需要引用到常量池中的其他内容解析出编号即可。再看一个常量字符串的序列【01 00 06 3c 69 6e 69 74 3e】01表明是utf8字符串字面量,【00 06】表示长度为6,后面6位转为字符串就是<init>,这是类实例化时调用的方法。就这样一次进行 33 次,就可以得到整个常量池的信息。
在常量池中,类,字段和方法描述符:
| 类和接口名,使用全限定名称 | 把 点. 换成 /,比如 java.lang.Object 全限定名:java/lang/Object |
| 方法,字段,局部变量名,使用非全限定名称 | 当前类的相对名称,比如 Demo |
| 字段描述符 | 描述字段类型 |
| B,C,D,F,I,[ | 分别对应 byte,char,double,float,int,数组类型 |
| J,S,Z,V,L class_name | 分别对应 long,short,boolean,void,class_name对象类型 |
| 方法描述符,描述参数和返回值类型 | ( {ParameterDescriptor} ) ReturnDescriptor |
| ()V | Object m(int i, double d, Thread t) {...} |
| 无参返回值为空 | (IDLjava/lang/Thread;)Ljava/lang/Object; |
3. Class 父类,访问权限和接口信息
根据ClassFIle的结构,接下来是 2 字节的 access_flags,【access_flags】表示类或接口的访问权限,具体的标志位如下表:
| 标志名称 | 标志值 | 含义 |
| ACC_PUBLIC | 0x0001 | 声明为 public |
| ACC_FINAL | 0x0010 | 声明为 final,不能被继承 |
| ACC_SUPER | 0x0020 | 表明使用 invokespecial 调用父类方法 |
| ACC_INTERFACE | 0x0200 | 声明为 接口 |
| ACC_ABSTRACT | 0x0400 | 声明为抽象类,不能实例化 |
| ACC_SYNTHETIC | 0x1000 | 动态生成的代码,不是用户由编写的 |
| ACC_ANNOTATION | 0x2000 | 声明 注解 |
| ACC_ENUM | 0x4000 | 声明 枚举 |

access_flags使用 2 个字节表示,由上图可知为 0x0021,转换成二进制 【0000 0000 0020 0001】,查上表可得此类为 ACC_PUBLIC,ACC_SUPER。
接下来就是当前类索引,父类索引和接口索引描述,其中当前类和父类描述都是通过 2 个字节,2 字节的 this_class,2 字节的 super_class。这里this_calss是 #5 表示引用常量池中第5个常量,从常量池中可以看到就是当前的Demo类;super_class是 #6 ,查常量池可知,#6 代表java.lang.Object对象,这说明了,Object是Java中所有类的直接或间接父类。
如果类实现了接口,那么接口的信息会在接下来的字节表示出来,这里没有实现接口,字节全为 0。如果实现接口的话,首先 2 个字节表示接口的个数,接下来就是具体内容,每个接口信息占 2 个字节,并引用常量池中的Class类型的元素,并按照源代码中实现的顺序排列。
4. Class 字段及其属性
类字段信息主要有,访问控制和字段类型。下表是字段的访问控制说明:
| 字段访问标志 | 标志值 | 含义 |
| ACC_PUBLIC | 0x0001 | 声明为 public |
| ACC_PRIVATE | 0x0002 | 声明为 private |
| ACC_PROTECTED | 0x0004 | 表明为 protected |
| ACC_STATIC | 0x0008 | 声明为 static |
| ACC_FINAL | 0x0010 | 声明为 final |
| ACC_VOLATILE | 0x0040 | 声明为 volatitle |
| ACC_TRANSIENT | 0x0080 | 声明为 transient 不序列化 |
| ACC_SYNTHETIC | 0x1000 | 表示由编译器生成 |
| ACC_ENUM | 0x4000 | 声明为 enum |
字段有一个 ConstantValue 属性,只有当字段是 ACC_STATIC 时,此属性才生效,并且长度固定为 2,引用常量池中的 int, short, char, byte, boolean, long, float, double, String 类型字面量。
5. Class 方法及其属性
描述了方法的参数及其类型,返回值,字节码,以及最大操作数栈深度,局部变量个数,参数个数,访问控制等信息,首先看一下,方法的访问标志:
| 方法访问标志 | 标志值 | 含义 |
| ACC_PUBLIC | 0x0001 | 声明为 public |
| ACC_PRIVATE | 0x0002 | 声明为 private |
| ACC_PROTECTED | 0x0004 | 表明为 protected |
| ACC_STATIC | 0x0008 | 声明为 static |
| ACC_FINAL | 0x0010 | 声明为 final,不能重写 |
| ACC_SYNCHRONIZED | 0x0020 | synchronized,方法由管程同步 |
| ACC_BRIDGE | 0x0040 | 方法由编译器产生 |
| ACC_VARARGS | 0x0080 | 表示方法带有变长参数 |
| ACC_NATIVE | 0x4100 | 非 java 语言的本地方法 |
| ACC_ABSTRACT | 0x0400 | abstract,方法没有具体实现 |
| ACC_STRICT | 0x0800 | strictfp,方法使用 FP-strict 浮点格式 |
| ACC_SYNTHETIC | 0x1000 | 方法在源文件中不出现,由编译器产生 |
方法的属性,比较重要,里面具有 JVM 要执行的指令信息。
5.1 Exceptions 描述方法可能抛出的异常
5.2 Code 属性
Code 代码属性是最要的属性,描述方法的字节码或辅助信息,如果是本地方法或抽象方法,那么不能拥有此属性。Code 结构如下:
Code 的相关属性:
- LocalVariableTable:局部变量表
- LocalVariableTypeTable:也是局部变量表,跟泛型有关
- LineNumberTable:源代码行号与字节码行号对应关系
- StackMapTable:在 JVM 验证类型时使用
6. 其他属性信息
类相关属性:
- InnerClasses:内部类属性
- EnclosingMethod:当类为内部类或局部类是此属性才存在,并且一个类至多有一个
- SourceFile:定长,只能有一个
- SourceDebugExtension:扩展调试信息
- BootstrapMethods:保存 invokedynamic 指令引用的引导方法限定符
方法属性:
- RuntimeVisibleParameterAnnotations:方法参数的注解,可被反射 API 使用
- RuntimeInvisibleParameterAnnotations:不能被反射 API 使用
- MethodParameters:方法参数信息
- AnnotationDefault:元素注解默认值
共有属性:
- Deprecated:表面字段,方法和类将会在后续版本被替换
- Synthetic:源代码未出现的类成员,必须标记成合成属性,并且长度固定为 0
- Signature:描述类,方法或接口的泛型信息
- RuntimeVisibleAnnotations:保存类,字段和方法可见注解,必须保证可被反射使用
- RuntimeInvisibleAnnotations:不能被反射 API 访问
- RuntimeVisibleTypeAnnotations:
- RuntimeInvisibleTypeAnnotations:
7. 实例描述
(1)常量池

(2)方法描述

本文使用的Demo源码和Class文件:Demo.rar
JVM-Class文件的更多相关文章
- 生成jvm快照文件
原文:https://blog.csdn.net/jijianshuai/article/details/79128033 -Xmx20m -Xms5m -XX:HeapDumpOnOutofMem ...
- 获取JVM转储文件的Java工具类
在上期文章如何获取JVM堆转储文件中,介绍了几种方法获取JVM的转储文件,其中编程方法是里面唯一一个从JVM内部获取的方法.这里就不演示了其他方法获取正在运行的应用程序的堆转储,重点放在了使用编程来获 ...
- [jvm] -- 类文件结构篇
类文件结构 结构图 魔数 头四个字节,作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件. Class 文件版本 第五和第六是次版本号,第七和第八是主版本号. 高版本的 Java 虚拟机 ...
- JVM中class文件探索与解析(一)
一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正. 废话不多说,一起来看看JVM中类文件是如何 ...
- JVM中class文件探索与解析
一直想成为一名优秀的架构师的我,转眼已经工作快两年了,对于java内核了解甚少,闲来时间,看看JVM,吧自己的一些研究写下来供大家参考,有不对的地方请指正. 废话不多说,一起来看看JVM中类文件是如何 ...
- Class文件和JVM的恩怨情仇
类的加载时机 现在我们例子中生成的两个.class文件都会直接被加载到JVM中吗?? 虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(class文件加载到JVM中): 创建类的实例 ...
- 干货分享丨jvm系列:dump文件深度分析
摘要:java内存dump是jvm运行时内存的一份快照,利用它可以分析是否存在内存浪费,可以检查内存管理是否合理,当发生OOM的时候,可以找出问题的原因.那么dump文件的内容是什么样的呢? JVM ...
- JVM学习(1)——通过实例总结Java虚拟机的运行机制
俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: JVM的历史 JVM的运行流程简介 JVM的组成(基于 Java 7) JVM调优参数:-Xmx和-Xms ...
- JVM原理和优化
JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界 ...
- 在执行Java命令或eclipse启动程序,提示报错’jvm.cfg无法找到’的解决办法
一.问题背景 昨天debug代码的时候,突然发现无法启动程序了.每次启动程序的时候均报如下错误:(回家以后重现了下这个问题.发现不同电脑,所在的lib下的文件夹不一样,应该和jdk安装时硬件的情况有关 ...
随机推荐
- C# 在word中查找及替换文本
C# 在word中查找及替换文本 在处理word文档时,很多人都会用到查找和替换功能.尤其是在处理庞大的word文档的时候,Microsoft word的查找替换功能就变得尤为重要,它不仅能让我们轻易 ...
- 用Spire.doc来合并邮件
用Spire.doc来合并邮件 让我们想象一下这样的场景:你在一家IT公司上班.某天公司的某一产品大幅度升级了.然后你需要通知所有的客户.这真是很长的名单.一个个的通知他们是有点蠢的,因为这要花费 ...
- 利用Eclipse使用Java OpenCV(Using OpenCV Java with Eclipse)
最近在上计算机视觉这门课程用到了OpenCV,于是找到了"Using OpenCV Java with Eclipse"这篇博文,是英文的,我将它翻译如下与大家分享 正文: 从2. ...
- 解析C#类中的构造函数
<解析C#类中的构造函数> 一. C#中的构造函数概述: C#中类包含数据成员和函数成员.函数成员提供了操作类中数据的某些功能,包括方法.属性.构造器和终结器.运算符和索引器. 构造函数 ...
- struct 大小计算
结构体是一种复合数据类型,通常编译器会自动的进行其成员变量的对齐,已提高数据存取的效率.在默认情况下,编译器为结构体的成员按照自然对齐(natural alignment)条方式分配存储空间,各个成员 ...
- 从零开始,搭建博客系统MVC5+EF6搭建框架(5),博客详情页、留言、轮播图管理、右侧统计博文
一.博客系统进度回顾 上一遍博客介绍到,系统已经实现到了发布以及前台布局展示,接下来就是实现一些,详情页,留言.轮播图管理.右侧博文统计信息实现. 二.博客系统详情页实现 2.1先来看看详情页展示的效 ...
- 【原创】SQL常用函数
--值类型转换 ) --时间差 ,GETDATE()),GETDATE()) --加减指定时间 ,GETDATE()) --获取系统日期 select GETDATE() --生成不重复Id sele ...
- SqlServer -- char与varchar、nchar、N
一.char与varchar 带var的表示的是:可变长度,会根据实际存储数据的大小动态重新分配存储空间,相对来说节省存储空间. 不带var的表示的是:固定长度,存储3字符也是要占用10个字节的,会自 ...
- 用Kotlin改写PHP程序是什么样的体验
学Kotlin其实要看:http://kotlinlang.org/docs/kotlin-docs.pdf 在线版是不完整的!!!少了一些章节,会有点难看懂后面的文档. 我选择了WordPress里 ...
- 关于Spring/Hibernate 3.x升级4.x的小问题
情景: 之前版本 现在版本 JDK 1.7 1.8 Tomcat v7.0 v8.0 Spring 3.x 4.x Hibernate 3.x 4.x MySQL 忘了 5.1.53 分析: 如果 ...