Java类文件的结构
Class文件是以8位字节为基础单位的二进制流,各部分中间没有分隔符。遇到8位字节以上的空间数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
Class文件采用类似C语言的伪结构体来存储,这种伪结构体只有两种数据类型:无符号数和表。无符号数以u1,u2,u4,u8四种,数字代表字节数。可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表习惯以“info”结尾。表用于描述有层次关系的复合结构数据,整个Class文件本质上就是一张表。
总览如下:
魔数与Class文件的版本 |
1.魔数0XCAFEBABE 2.次版本号和主版本号 |
常量池 |
1.类和接口的全限定名 2.字段的名称和描述符 3.方法的名称和描述符 |
访问标志 |
1.类的访问信息 2.接口的访问信息 |
类索引、父类索引 和接口索引集合 |
存储类、父类、接口的 文件索引 |
字段表集合 |
1.字段作用域 2.是否static 3.可变性 4.并发可见性 5.可否被序列化 6.字段数据类型 7.字段名称 |
方法表集合 |
1.访问标志 2.名称索引 3.描述符索引 4.属性表集合 |
属性表集合 |
1.Code属性 2.Exceptions属性 3.LocalVariableTable属性 4.LineNumberTable属性 5.SourceFile属性 6.ConstantValue属性 7.InnerClasses属性 8.Deprecated和Synthetic属性 9.StackMapTable属性 10.Signature属性 11.BootstrapMethods属性 |
1.魔术与Class文件版本
每个Class文件头四个字节称为魔数(Magic Number),作用是确定这个文件是不是一个Class文件,其值为0xCAFEBABE。紧跟着其后的4个字节存储的是Class文件的版本号:第5个和第6个字节是次版本号,第7个和第8个字节是主版本号。
2.常量池
紧接着主版本号之后的是常量池入口,入口处放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count),计数器从1开始,0是为了满足后面某些值项常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”。
常量池之中主要存放两大类常量:字面量(Literal) 和 符号引用(Symbolic References) 。字面量比较接近于Java语言层面的常量概念。而符号引用则属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
常量池中的每一项常量都是一个表,共有14总结构各不相同的表结构数据,这11种表都有一个共同的特点,就是表开始第一位是一个u1类型的标志位(tag,取值为1置12,缺少标志为2的数据类型),14种常量具体含义如下:
类型 | 标志 | 描述 |
CONSTANT_Utf8_info | 1 | UTF-8编码的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮点型字面量 |
CONSTANT_Long_info | 5 | 长整型字面量 |
CONSTANT_Double_info | 6 | 长整型字面量 |
CONSTANT_Class_info | 7 | 类或接口的符号引用 |
CONSTANT_String_info | 8 | 字符串类型字面量 |
CONSTANT_Fieldref_info | 9 | 字段符号引用 |
CONSTANT_Methodref_info | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标识方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 标识一个动态方法调用点 |
3.访问标志
常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息。具体标志为以及标志的含义如下:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK 1.02发生过改变,为了区别这条指令使用哪种语意,JDK 1.02之后编译出来的类这个标志都必须为真 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
access_flags 中一共有16个标志位可以使用,当前之定义了其中的8个,没用使用到的标志位要求一律为0。
4.类索引、父类索引与接口索引集合
类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interface)是一组u2类型的数据集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。因为Java不支持多重继承,所以父类索引只有一个,除了java.lang.Object外,所有Java类都有父类,因此除了java.lang.Object,所有java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,顺序为implements后面从左到右排列在接口索引集合中。
类索引、父类索引和接口索引都按顺序排列在访问标志之后,类索引和父类索引引用两个u2索引值表示,它们各自指向一个类型为CONSTANT_Class_info的类描述符常量,通过CONSTANT_Class_info类型常量中的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字字符串。
对于接口索引集合,入口第一项——u2类型的数据为接口计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,则该计数器值为0,后面索引不在占用任何字节。
5.字段表集合
字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段信息包括:字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称,以上修饰符都是布尔类型。字段表结构如图:
类型 | 名称 | 数量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
access_flags是字段访问标志,标志如下:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 字段是否public |
ACC_PRIVATE | 0x0002 | 字段是否private |
ACC_PROTECTED | 0x0004 | 字段是否protected |
ACC_STATIC | 0x0008 | 字段是否static |
ACC_FINAL | 0x0010 | 字段是否final |
ACC_VOLATILE | 0x0040 | 字段是否volatile |
ACC_TRANSIENT | 0x0080 | 字段是否transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动产生的 |
ACC_ENUM | 0x4000 | 字段是否enum |
name_index 和 descriptor_index都是对常量池的引用,分别代表这字段的简单名称以及字段和方法的描述符。
attribute_info用于存一些额外信息,如final static int m =123;
方法和字段的描述符作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根描述规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,对象类型使用字符L加对象的全限定名来表示。
标识字符 | 含义 |
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型 |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 特殊类型void |
L | 对象类型,如Ljava/lang/Object |
比如方法 int indexOf(char[]source,int sourceOffset,int sourceCount,char[]target,int targetOffset,int targetCount,int fromIndex)描述符为”([CII[CIII)I”
6.方法表集合
方法的描述和字段的描述几乎采用了完全一致的方式。方法表一次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项。方法表如下:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 方法是否为public |
ACC_PRIVATE | 0x0002 | 方法是否为private |
ACC_PROTECTED | 0x0004 | 方法是否为protected |
ACC_STATIC | 0x0008 | 方法是否为static |
ACC_SYNCHRONIZED | 0x0020 | 方法是否为synchronized |
ACC_BRIDGE | 0x0040 | 方法是否是由编译器产生的桥接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定参数 |
ACC_NATIVE | 0x0100 | 方法是否为native |
ACC_ABSTRACT | 0x0400 | 方法是否为abstract |
ACC_STRICTFP | 0x0800 | 方法是否为strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否是由编译器自动产生的 |
ACC_FINAL | 0x0010 | 方法是否为final |
7.属性表集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,用于描述某些场景专有的信息。
预定义的属性如下:
属性名称 | 使用位置 | 含义 |
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类、方法表、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号和字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法局部变量描述 |
StackMapTable | Code属性 | JDK1.6新增,供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配 |
Signature | 类、方法表、字段表 | JDK1.5新增,用于支持泛型的情况下的方法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | JDK1.6新增,用于存储额外的调试信息。比如JSP调试 |
Synthetic | 类、方发表、字段表 | 表示方法或字段为编译器自动生成的 |
LocalVariableTypeTable | 类 | JDK1.5新增,使用特征签名代替描述符 |
RuntimeVisibleAnnotations | 类、方法表、字段表 | JDK1.5新增,为动态注解提供支持 |
RuntimeVisibleParameterAnnotations | 方法表 | JDK1.5新增,类似RuntimeVisibleAnnotations,但作用对象为方法参数 |
AnnotationDefault | 方法表 | JDK1.5新增,用于记录注解类元素的默认值 |
RuntimeInvisibleAnnotations | 方法表 | JDK1.5新增,作用和RuntimeVisibleAnnotations属性作用相反,用于指定哪些注解是运行时不可见的 |
RuntimeInvisibleParameterAnnotations | 方法表 | JDK1.5新增,类似RuntimeInVisibleAnnotations,但作用对象是方法参数 |
BootstrapMethods | 类文件 | JDK1.7新增,用于保存invokedynamic指令引用的引导方法限定符 |
Java类文件的结构的更多相关文章
- 类文件结构-----Class类文件的结构
①无关性的基石 “与平台无关的”得理想最终实现在操作系统的应用层上:Sun公司和其他虚拟机提供商发布了许多可以在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现了程序 ...
- JVM(五) class类文件的结构
概述 class类文件的结构可见下面这样图(出处见参考资料),可以参照下面的例子,对应十六进制码,找出找出相应的信息. 其中u2 , u4 表示的意思是占用两个字节和占用四个字节,下面我们将会各项说明 ...
- 类文件的结构、JVM 的类加载过程、类加载机制、类加载器、双亲委派模型
一.类文件的结构 我们都知道,各种不同平台的虚拟机,都支持 "字节码 Byte Code" 这种程序存储格式,这构成了 Java 平台无关性的基石.甚至现在平台无关性也开始演变出 ...
- Java类文件最大限制
今天在往一个jsp文件里添加代码时,项目跑起来访问这个jsp时报错.. The code of method _jspService(HttpServletRequest, HttpServletRe ...
- javap -- Java 类文件解析器
参考文档 http://blog.chinaunix.net/uid-692788-id-2681132.html http://docs.oracle.com/javase/7/docs/techn ...
- Eclipse或MyEclipse没有在java类文件上显示Spring图标的问题
Eclipse或MyEclipse没有在java类文件上显示接口图标的问题解决办法: 前: 后:
- Class类文件的结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有任何分隔符.Class文件的结构只有两种数据类型:无符号数和表.无符号数以u1.u2. ...
- php调用java类文件
最近在折腾php调用java类,网上查阅资料,最终选用JavaBridge,遇到的第一个问题是java_require() 函数引入自定义java打包而成的jar文件,在新版本的JavaBridge中 ...
- DOC下编译和运行带有包的java类文件
前言: 带有包名的java类在DOC下编译可以成功,但是运行出错 错误: 找不到或无法加载主类 com.soanl.socket.MyServer D盘temp文件下有个Hello.java文件,包 ...
随机推荐
- 【转发】如何使用NPM?CNPM又是什么?
转发:https://www.jianshu.com/p/f581cf9360a2 背景介绍 什么是npm? npm(node package manager)是nodejs的包管理器,用于node插 ...
- LOJ 6092
这个题也很没意思 发现q那么大没有用, 不重复的询问有26*n种 所以记录一下就好了 #include<bits/stdc++.h> using namespace std; #defin ...
- 用户注册之后,通过网易邮箱服务器(smtp.163.com)发送电子邮箱到注册者邮箱的的确认通知短信.(可根据需求自行调整)
Member 是数据实体,穿过来的也就是当前注册用户的信息. 存储的数据一定要有邮箱信息 private void SendAuthCodeToMember(Member member) ...
- js计算发表的时间...分钟/小时以前/以后
网上找的都好复杂,这本来就是个粗略显示通俗的时间,绕来绕去都晕了 function timeAgo(o){ var n=new Date().getTime(); var f=n-o; var bs= ...
- Hibernate 检索方式之 HQL 检索方式
HQL(Hibernate Query Language) 是面向对象的查询语言,它和 SQL 查询语言有些相似.在 Hibernate 提供的各种检索方式中,HQL 是使用最广的一种检索方式,它有如 ...
- Linux 下的两个特殊的文件 -- /dev/null 和 /dev/zero 简介及对比
1.概论 -- 来自维基的解释 /dev/null : 在类Unix系统中,/dev/null,或称空设备,是一个特殊的设备文件,它丢弃一切写入其中的数据(但报告写入操作成功),读取它则会立即得到一 ...
- PHP调用微信wx_JSSDK录音并播放,
<?php require_once "jssdk.php"; $jssdk = new JSSDK("wx7a862ec806328ca2", &quo ...
- LINUX监控-spotlight
这里连接的user不能是root(spotlight需要一个具有root权限的用户,但是又不允许是root),所以需要给要连接的linux端创建一个有root权限的用户,在linux主机创建了root ...
- centos为docker配置加速器
国内拉去docker镜像慢得可怜,为了解决这个问题,可为docker配置加速器. 1.修改daemon配置文件 sudo mkdir -p /etc/dockervim /etc/docker/dae ...
- icpc2018焦作-I. Distance
第一发又超时了... 题目大意:给你n个点,然后给你n-1的数,表示两两距离,然后让你输出n个答案,第i个答案表示从这n个点里面挑i个点,然后这i个点两两之间会有一个距离,答案要求这些距离和的最大值. ...