1.Class文件
1.0 字符与字节
**ASCII码:**一个英文字母(不分大小写)占一个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数。换算为十进制 ,最小值-128,最大值127。如一个ASCII码就是一个字节。
**UTF-8编码:**一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节
**Unicode编码:**一个英文等于两个字节,一个中文(含繁体)等于两个字节。中文标点占两个字节,英文标点占两个字节
1.1 Class文件简介和发展历史
javac编译java文件
class 二进制查看CA FE BA BY开头(魔数).
class的规范从第1版到第8版改动很小.
1.2 Class文件结构概述
1.2.1 概述
- 理解下CA FE BA BE:字符C为16进制(4位,0~15),CA(2个4位,8位,1个字节). 是无符号数.
- Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有添加任何分隔符,整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。
- 当遇到8位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
- Class文件中有两种数据类型,分别是无符号数(约比喻基础数据类型)和表(约比喻引用数据类型,表可以包含无符号数和表)。
优点: 节省存储空间,提高程序性能
1.2.2 Class文件结构
- 魔数
- Class文件版本
- 常量池
- 访问标志
- 类索引,父类索引,接口索引集合
- 字段表集合
- 方法表集合
- 属性表集合
Class File format
type | descriptor[number] | remark |
---|---|---|
u4 | magic[1] | 0xCAFEBABE |
u2 | minor_version[1] | |
u2 | major_version[1] | |
u2 | constant_pool_count[1] | |
cp_info | constant_pool[1][cosntant_pool_count – 1] | index 0 is invalid |
u2 | access_flags[1] | |
u2 | this_class[1] | |
u2 | super_class[1] | |
u2 | interfaces_count[1] | |
u2 | interfaces[interfaces_count] | |
u2 | fields_count[1] | |
field_info | fields[fields_count] | |
u2 | methods_count[1] | |
method_info | methods[methods_count] | |
u2 | attributes_count[1] | |
attribute_info | attributes[attributes_count] |
一个U代表一个字节(两个字符,8个bit). u4代表4个字节
1.3 Class文件设计理念以及意义
语法->编译器->字节码->JVM
自己实现语法和编译器: Clojure,groovy,Jruby,Jython,Scala(运行在jvm之上的语言)
2 文件结构
//源码
public class HelloWorld {
public static void main(String []args) {
System.out.println("Hello World ..");
}
}
复制代码
编译后class 解读的原生class文件链接 ; 图片链接
通过ue查看class文件:
位(bit):0或1
字节(Byte): 8个二进制位构成一个字节,存储空间的基本单位.1个字节存一个英文字符或者半个汉字.
16进制数字:每一个16进制数字占4个bit,所以2个16进制字符占8个bit为一个字节.
2.1 文件结构-魔数,版本号(magic,minor_version,major_version)
type | descriptor[number] | remark |
---|---|---|
u4 | magic[1] | 0xCAFEBABE |
u2 | minor_version[1] | |
u2 | major_version[1] |
CAFE BABE 4个字节开头1行0列,1行1列,1行2列,1行3列;
然后版本号:占4个字节:
前2字节(5,6位)是小版本号,
后2字节是大版本号(7,8位).
大版本号对应数字(十进制的数字,class通过二进制文件查看的为16进制,需要转换)
JDK 1.8 = 52
JDK 1.7 = 51
JDK 1.6 = 50
JDK 1.5 = 49
JDK 1.4 = 48
JDK 1.3 = 47
JDK 1.2 = 46
JDK 1.1 = 45
在上图中的HelloWorld.class中1行4列,1行5列,1行6列,1行7列是00 00 00 34—十进制—>0,52,也就是JDK1.8
2.2 文件结构-常量池(constant_pool_count,constant_pool)
type | descriptor[number] | remark |
---|---|---|
u2 | constant_pool_count[1] | |
cp_info | constant_pool[cosntant_pool_count – 1] | index 0 is invalid |
参考-Java字节码(.class文件)格式详解(一)[这个常量池画的很清楚]
2.2.1 常量池类型/含义列表
常量池中每一项常量都是一个表(14种表)
类型 | 标志 | 描述 |
---|---|---|
无 | 0 | 表示不引用任何常量 |
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 | 字段或方法的符号引用 |
__ | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 标志方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
格式模板==cp_info format(长度标示)
type | descriptor | remark |
---|---|---|
u1 | tag | 该类型数目 |
u1 | info[] | 表的集合 |
1==CONSTANT_Utf8_info
记录字符串的值(represent constant string values. String content is encoded in modified UTF-8.)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Utf8 (1) |
u2 | length | bytes所代表的字符串的长度 |
u1 | bytes[length] | 字符串的byte数据,可以通过DataInputStream中的readUtf()方法(实例方法或静态方法读取该二进制的字符串的值。) |
3==CONSTANT_Integer_info
用于记录int类型的常量值(represent 4-byte numeric (int) constants:)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Integer (3) |
u4 | bytes | 整型常量值 |
4==CONSTANT_Float_info
用于记录float类型的常量值(represent 4-byte numeric (float) constants:)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Float(4) |
u4 | bytes | 单精度浮点型常量值 |
5==CONSTANT_Long_info
用于记录long类型的常量值(represent 8-byte numeric (long) constants:)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Long (5) |
u4 | high_bytes | 长整型的高四位值 |
u4 | low_bytes | 长整型的低四位值 |
6==CONSTANT_Double_info
用于记录double类型的常量值(represent 8-byte numeric (double) constants:)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Double(6) |
u4 | high_bytes | 双精度浮点的高四位值 |
u4 | low_bytes | 双精度浮点的低四位值 |
7==CONSTANT_Class_info format
用于记录类或接口名(used to represent a class or an interface)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Class (7) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。 |
8==CONSTANT_String_info
用于记录常量字符串的值(represent constant objects of the type String:)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_String(8) |
u2 | string_index | constant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。 |
9==CONSTANT_Fieldref_info
用于记录字段信息(包括类或接口中定义的字段以及代码中使用到的字段)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Fieldref(9) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。 |
10==CONSTANT_Methodref_info
用于记录方法信息(包括类中定义的方法以及代码中使用到的方法)
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_Methodref(10) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。 |
11==CONSTANT_InterfaceMethodref_info
用于记录接口中的方法信息(包括接口中定义的方法以及代码中使用到的方法)。
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_InterfaceMethodref(11) |
u2 | class_index | constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。 |
u2 | name_and_type_index | constant_pool中的索引,CONSTANT_NameAndType_info类型。指定接口中的方法名(name)和方法描述符(descriptor)。 |
12==CONSTANT_NameAndType_info
记录方法或字段的名称(name)和描述符(descriptor)(represent a field or method, without indicating which class or interface type it belongs to:)。
type | descriptor | remark |
---|---|---|
u1 | tag | CONSTANT_NameAndType (12) |
u2 | name_index | constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。 |
u2 | descriptor_index | constant_pool中的索引,CONSTANT_utf8_info类型。指定字段或方法的描述符(见附录C) |
16==CONSTANT_MethodType_info
暂缺
18==CONSTANT_InvokeDynamic_info
暂缺
2.2.3 解读
常量池长度(constant_pool_count)
占u2(2个字节):
HelloWorld.class的文件中 1行8列,1行9列 是00 22;00 22—十进制–>34个(组)常量.
第一组常量
1行a 即 0A—十进制—>10(10代表指向10=CONSTANT_Methodref_info,并且0A是第一个常量池(常量池编号为1)),
此时查看10=CONSTANT_Methodref_info的结构规范2.2.1中的10[u1|tag,u2|classindex,u2|name_and_type_index],
那就是后2个字节 1行b,1行c 即00 06为CONSTANT_Methodref_info的classindex,00 06–十进制–>6(指向第6个常量池位置).
再后2个字节 1行d,1行e 即00 14为CONSTANT_Methodref_info的name_and_type_index,00 14–十进制–>20(指向第20个常量池位置)
第二组常量
1行f列 即09—十进制—>9(9代表9=CONSTANT_Fieldref_info,并且09是第二个常量池);
此时查看9=CONSTANT_Fieldref_info的规范结构[u1|tag,u2|class_index,u2|name_and_type_index],
那就是后2个字节 2行0列,2行1列 即 00 15为class_index,00 15—十进制—>21(指向21号位置),
在后2个字节 2行2列,2行3列 即00 16为name_and_type_index,00 16—十进制—>22(指向22号位置),
第三组常量
2行4列 即08—十进制—>8(代表8=CONSTANT_String_info,并且08是第三个常量池);
此时查看8==CONSTANT_String_info的规范结构[u1|tag,u2|string_index],
那就是后2个字节 2行5列,2行6列 即00 17为string_index —十进制—>23(指向23号位置)
第四组常量
2行7列 即0A—十进制—>10(10=CONSTANT_Methodref_info,并且0A是第四个常量池);
此时查看10=CONSTANT_Methodref_info的结构规范[u1|tag,u2|classindex,u2|name_and_type_index],
那就是后2个字节 2行8,2行9 即00 18为classindex,00 18–十进制–>24(指向第24个常量池位置).
再后2个字节 2行a,2行b 即00 19为name_and_type_index,00 19–十进制–>25(指向第25个常量池位置)
第五组常量
2行c列 即07—十进制—>7(7=CONSTANT_Class_info format,并且07是第五个常量池);
此时查看7==CONSTANT_Class_info format的规范结构[u1|tag,u2|name_index],
那就是后2个字符 2行d列,2行e列 即00 1A为name_index;00 1A—十进制—>26(指向第26个常量池位置)
第六组常量
2行f列 即07—十进制—>7(7=CONSTANT_Class_info format,并且07是第六个常量池);
此时查看7==CONSTANT_Class_info format的规范结构[u1|tag,u2|name_index],
那就是后2个字符 3行0列,3行1列 即00 1B为name_index;00 1B—十进制—>27(指向第27个常量池位置)
第七组常量
3行2列 即01—十进制—>1(1=CONSTANT_Utf8_info,并且01是第七个常量池);
此时查看1==CONSTANT_Utf8_info的结构规范[u1|tag,u2|length,u1|bytes[length]],
那就是后2列 3行3列,3行4列 即00 06为length,00 06—十进制—>6,
再后6和长度 3行5列,3行6列,3行7列,3行8列,3行9列,3行10列 即3C 69 6E 74 3E为lbytes[length],
3C 69 6E 74 3E—十进制—>60 89 105 110 116 62 —对应字符—>
… …
可以一直往后读,直至34个(组)常量.
然后就可用工具读了. java工具 javap,读完如下:
会发现:
#1 指向#6(java/lang/Object),#20(#7(),#8(()V)),然后后面就显示了#6,#20的值.
javap -verbose HelloWorld.class
Classfile /D:/workspace/***/HelloWorld.class
Last modified 2017-12-27; size 536 bytes
MD5 checksum a77b7f0d7e173b1c9b6040311535c45d
Compiled from "HelloWorld.java"
public class HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/Prin
Stream;
#3 = String #23 // Hello World ..
#4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava
lang/String;)V
#5 = Class #26 // HelloWorld
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 LHelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 SourceFile
#19 = Utf8 HelloWorld.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = Class #28 // java/lang/System
#22 = NameAndType #29:#30 // out:Ljava/io/PrintStream;
#23 = Utf8 Hello World ..
#24 = Class #31 // java/io/PrintStream
#25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V
#26 = Utf8 HelloWorld
#27 = Utf8 java/lang/Object
#28 = Utf8 java/lang/System
#29 = Utf8 out
#30 = Utf8 Ljava/io/PrintStream;
#31 = Utf8 java/io/PrintStream
#32 = Utf8 println
#33 = Utf8 (Ljava/lang/String;)V
{
public HelloWorld();
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 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LHelloWorld;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Lja
a/io/PrintStream;
3: ldc #3 // String Hello World ..
5: invokevirtual #4 // Method java/io/PrintStream.pri
tln:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"
复制代码
2.3 文件结构-访问标志
type | descriptor[number] | remark |
---|---|---|
u2 | access_flags[1] |
2.3.1 访问标志概述
常量池后面就是访问标志。
u2|access_flags,占用2个字节(16位).
access_flags:
标志名称 | 标志值 | 含义 | 代码中表示 |
---|---|---|---|
ACC_PUBLIC | 0x00 01 | 是否为Public类型 | public |
ACC_FINAL | 0x00 10 | 是否被声明为final,只有类可以设置 | final |
ACC_SUPER | 0x00 20 | 是否允许使用invokespecial字节码指令的新语义. | extends |
ACC_INTERFACE | 0x02 00 | 标志这是一个接口 | interface |
ACC_ABSTRACT | 0x04 00 | 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假 | abstract |
ACC_SYNTHETIC | 0x10 00 | 标志这个类并非由用户代码产生 | 所有类型 |
ACC_ANNOTATION | 0x20 00 | 标志这是一个注解 | annotation |
ACC_ENUM | 0x40 00 | 标志这是一个枚举 | enum |
ACC_PUBLIC(public)、ACC_PRIVATE(private)、ACC_PROTECTED(protected)、ACC_FINAL(final)、ACC_SUPER(extends)、ACC_INTERFACE(接口)、ACC_ABSTRACT(抽象类)、ACC_ANNOTATION(注解类型)、ACC_ENUM(枚举类型)、ACC_DEPRECATED(标记了@Deprecated注解的类)、ACC_SYNTHETIC
复制代码
注意:图片中的ACC_INTERFACE的值不对,应该以上面表格为准.ACC_INTERFACE=0x0400.
2.3.2 解读
2.3.2.1 解读HelloWorld.class
根据2.2.3中根据javap工具解读的结果,能查看到最后一个常量:
#33 = Utf8 (Ljava/lang/String;)V
则再在HelloWorld-Class-Binary.png中找到#33的位置,则其后面就是访问标志的开始.
通过图片中会看到是从180h行0列开始为访问标志 .
180h行0列,180h行1列 即:00 21,对照2.3.1中的组合表.对表00 21=0x0001 & 0x0020,(即代表acc_public和acc_super[有个语意JDK1.0.2为界前后不同,如果acc_super有值1代表1.0.2之后版本,如果acc_super为0则表示1.0.2之前].),
所以含义代表 public Class.
也可以通过上面2.2.3 解读 javap -verbose 的结果中查看到
** flags: ACC_PUBLIC, ACC_SUPER **
2.3.2.2 解读HelloWorldInterface.class
//源码
public interface HelloWorldInterface {
}
复制代码
编译后class HelloWorldInterface.class
HelloWorldInterface.class 的javap -verbose 结果:
javap -verbose D:\workspace\HelloWorldInterface.class
Classfile /D:/workspace/HelloWorldInterface.class
Last modified 2017-12-30; size 119 bytes
MD5 checksum e8d0ce8260a978e105c60e9852245e1a
Compiled from "HelloWorldInterface.java"
public interface HelloWorldInterface
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
#1 = Class #5 // HelloWorldInterface
#2 = Class #6 // java/lang/Object
#3 = Utf8 SourceFile
#4 = Utf8 HelloWorldInterface.java
#5 = Utf8 HelloWorldInterface
#6 = Utf8 java/lang/Object
{
}
SourceFile: "HelloWorldInterface.java"
复制代码
通过查看二进制的java/lang/Object后的2个字节 060h行1列,060h行2列 即:06 01 = 0x04 & 0x02 & 0x00 ===>ACC_PUBLIC ACC_ABSTRACT ACC_INTERFACE ,也可以在javap的结果中查看
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT 得到同样的结果.
2.2.3 参考文章
JVM-class文件完全解析-访问标志 (简明扼要)
[深入字节码 — ASM 关键接口 ClassVisitor] my.oschina.net/u/1166271/b… (原理基础)
《Java虚拟机原理图解》1.3、class文件中的访问标志、类索引、父类索引、接口索引集合 [文中讲的很详尽]
2.4 文件结构-类索引(this_class,super_class,interfaces_count)
type | descriptor[number] | remark |
---|---|---|
u2 | this_class[1] | |
u2 | super_class[1] | |
u2 | interfaces_count | |
u2 | interfaces[interfaces_count] | |
### 2.4.1 概述 | ||
### 2.4.2 解读 | ||
接着解读HelloWorldInterface.class | ||
060h行3列,060h行4列 即00 01 是this_class,类,00 01—十进制—>1,表示指向常量池的第1个位置,查看2.3.2.2的javap的结果,看到#1 = Class, #5 // HelloWorldInterface (也就是最终指向了HelloWorldInterface) |
接着060h行5列,060h行6列 即00 02 是super_class,父类,00 02—十进制—>2,表示指向常量池的第2个位置,查看2.3.2.2的javape结果,看到#2 = Class, #6 // java/lang/Object (也就是最终指向了java/lang/Object)
接着060h行7列,060h行8列 即00 00 是interfaces_count,接口数量,00 00—十进制—>0,表示没有实现接口,
2.4.3 解读HelloExtendImp.class
2.4.3.1 信息准备
//源码
package classstruct;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class HelloExtendImp extends FileInputStream implements Runnable,ActionListener{
public HelloExtendImp(String name) throws FileNotFoundException {
super(name);
}
@Override
public void actionPerformed(ActionEvent e) {
}
@Override
public void run() {
}
}
复制代码
二进制查看如下:
D:\IdeaProjects\out\production\jvmStu\classstruct>javap -verb
loExtendImp.class
Classfile /D:/IdeaProjects/out/production/jvmStu/classstruct/
tendImp.class
Last modified 2017-12-31; size 703 bytes
MD5 checksum b0aba17a3e3cf2b32f0ad93f5d6e9b96
Compiled from "HelloExtendImp.java"
public class classstruct.HelloExtendImp extends java.io.FileInputStream i
ts java.lang.Runnable,java.awt.event.ActionListener
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#25 // java/io/FileInputStream."<in
java/lang/String;)V
#2 = Class #26 // classstruct/HelloExtendImp
#3 = Class #27 // java/io/FileInputStream
#4 = Class #28 // java/lang/Runnable
#5 = Class #29 // java/awt/event/ActionListene
#6 = Utf8 <init>
#7 = Utf8 (Ljava/lang/String;)V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lclassstruct/HelloExtendImp;
#13 = Utf8 name
#14 = Utf8 Ljava/lang/String;
#15 = Utf8 Exceptions
#16 = Class #30 // java/io/FileNotFoundExceptio
#17 = Utf8 actionPerformed
#18 = Utf8 (Ljava/awt/event/ActionEvent;)V
#19 = Utf8 e
#20 = Utf8 Ljava/awt/event/ActionEvent;
#21 = Utf8 run
#22 = Utf8 ()V
#23 = Utf8 SourceFile
#24 = Utf8 HelloExtendImp.java
#25 = NameAndType #6:#7 // "<init>":(Ljava/lang/String;
#26 = Utf8 classstruct/HelloExtendImp
#27 = Utf8 java/io/FileInputStream
#28 = Utf8 java/lang/Runnable
#29 = Utf8 java/awt/event/ActionListener
#30 = Utf8 java/io/FileNotFoundException
{
public classstruct.HelloExtendImp(java.lang.String) throws java.io.File
dException;
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #1 // Method java/io/FileInput
"<init>":(Ljava/lang/String;)V
5: return
LineNumberTable:
line 13: 0
line 14: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lclassstruct/HelloExtendImp;
0 6 1 name Ljava/lang/String;
Exceptions:
throws java.io.FileNotFoundException
public void actionPerformed(java.awt.event.ActionEvent);
descriptor: (Ljava/awt/event/ActionEvent;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=2, args_size=2
0: return
LineNumberTable:
line 19: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lclassstruct/HelloExtendImp;
0 1 1 e Ljava/awt/event/ActionEvent;
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 24: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lclassstruct/HelloExtendImp;
}
SourceFile: "HelloExtendImp.java"
复制代码
2.4.2.2 解读
通过javap结果查看到:
#30 = Utf8 java/io/FileNotFoundException(1d0h行0列,1d0h行2列)为常量池最后结束.
1d0h行3列,1d0h行4列 即00 21 为access_flags.00 21=0x0001 & 0x0020==>flags: ACC_PUBLIC, ACC_SUPER.(具体解析2.3中)
1d0h行5列,1d0h行6列 即00 02为this_class,指向常量池中的#02,查看javap结果即为类(classstruct/HelloExtendImpclassstruct/HelloExtendImp).
1d0h行7列,1d0h行8列 即00 03为super_class,指向常量池中的#03,查看javap结果即为父类(java/io/FileInputStream).
1d0h行9列,1d0h行a列 即00 02为interfaces_count,接口数目为2个.
1d0h行b列,1d0h行c列 即00 04为interface1,指向常量池中的#04,查看javap结果即为接口1(java/lang/Runnable).
1d0h行d列,1d0h行e列 即00 05为interface2,指向常量池中的#05,查看javap结果即为接口2(java/awt/event/ActionListene).
2.5 文件结构-字段(field_info)表集合
type | descriptor[number] | remark |
---|---|---|
u2 | fields_count[1] | |
field_info | fields[fields_count] |
2.5.1 概述
字段表(field_info)用于描述接口或者类中声明的变量.字段包括类级变量以及实例级变量,但是不包括在方法内部声明的局部变量.
2.5.1.1 字段(变量)表的结构
field_info:
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
2.5.1.2 字段访问标志
字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常相似的,都是一个u2的数据类型.
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 字段是否为public |
ACC_PRIVATE | 0x00 02 | 字段是否为private |
ACC_PROTECTED | 0x00 04 | 字段是否为protected |
ACC_STATIC | 0x00 08 | 字段是否为static |
ACC_FINAL | 0x00 10 | 字段是否为final |
ACC_VOLATILE | 0x00 40 | 字段是否为volatile |
ACC_TRANSTENT | 0x00 80 | 字段是否为transient |
ACC_SYNCHETIC | 0x10 00 | 字段是否为由编译器自动产生 |
ACC_ENUM | 0x40 00 | 字段是否为enum |
跟随access_flags标志的是两项索引值:name_index和descriptor_index,它们都是对常量池的引用,分别代表着字段的简单名称以及字段方法和方法的描述符. | ||
![]() |
||
![]() |
||
#### 2.5.1.3 描述符标志含义 | ||
描述符的作用是用来描述字段的数据类型,方法的参数列表(包括数量,类型以及顺序)和返回值.根据描述符规则,基本数据类型以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符加L加对象名的全限定名来表示. |
标志符 | 含义 |
---|---|
B | 基本数据类型byte |
C | 基本数据类型char |
D | 基本数据类型double |
F | 基本数据类型float |
I | 基本数据类型int |
J | 基本数据类型long |
S | 基本数据类型short |
Z | 基本数据类型boolean |
V | 基本数据类型void |
L | 对象类型 |
对于数组类型,每一维度将使用一个前置的"["字符来描述.如一个定义为"java.lang.Stirng[ ]"类型的二维数组,将被记录为:"[[Ljava/lang/Stirng",一个整型数组"int[]"将被记录为"[I".
用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述,参数列表按照参数的严格顺序放在一组小括号"()"之内.
字段表集合中不会列出从父类或者父接口中继承而来的字段,但有可能列出原来Java代码中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段.另外,在Java语言中字段是无法重载的,两个字段的数据类型,修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果连个字段的描述符不一致,那字段重名就是合法的.
2.5.2 解读
2.5.2.1 待解读信息
//源码
package classstruct;
public class HelloWorldField {
private int a ;
public byte b;
public static Object obj;
protected Object [] ojbs;
}
复制代码
二进制查看如下:
D:\workspace\javap -verbose HelloWorldField.class
Classfile /D:/workspace/HelloWorldField.class
Last modified 2017-12-31; size 398 bytes
MD5 checksum 8efd9fc928289285f5f89ce3c9f101d3
Compiled from "HelloWorldField.java"
public class classstruct.HelloWorldField
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#21 // java/lang/Object."<init>":()V
#2 = Class #22 // classstruct/HelloWorldField
#3 = Class #23 // java/lang/Object
#4 = Utf8 a
#5 = Utf8 I
#6 = Utf8 b
#7 = Utf8 B
#8 = Utf8 obj
#9 = Utf8 Ljava/lang/Object;
#10 = Utf8 ojbs
#11 = Utf8 [Ljava/lang/Object;
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lclassstruct/HelloWorldField;
#19 = Utf8 SourceFile
#20 = Utf8 HelloWorldField.java
#21 = NameAndType #12:#13 // "<init>":()V
#22 = Utf8 classstruct/HelloWorldField
#23 = Utf8 java/lang/Object
{
public byte b;
descriptor: B
flags: ACC_PUBLIC
public static java.lang.Object obj;
descriptor: Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_STATIC
protected java.lang.Object[] ojbs;
descriptor: [Ljava/lang/Object;
flags: ACC_PROTECTED
public classstruct.HelloWorldField();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lclassstruct/HelloWorldField;
}
SourceFile: "HelloWorldField.java"
复制代码
2.5.2.2 解读2.5.2.1
110h行b列,110h行c列 即00 21 为access_flags.00 21=0x0001 & 0x0020==>flags: ACC_PUBLIC,ACC_SUPER.(具体解析2.3中)
110h行d列,110h行e列 即00 02为this_class,指向常量池中的#02
110h行f列,120h行0列 即00 03为super_class,指向常量池中的#03
120h行1列,120h行2列 即00 00为interfaces_count,接口数目为0个.
接口数目将为0,则后面就没有interfaces了,然后就是fields_count了,即:
120h行3列,120h行4列 即00 04为4,指有4个field_info(类变量).
第一个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
120h行5列,120h行6列 即00 02为u2|access_flags,说明为private类型(根据2.5.1.2 字段访问标志).
120h行7列,120h行8列 即00 04为u2|name_index1,变量名称指向常量池中的#04,(#4 = Utf8 a)
120h行9列,120h行a列 即00 05为u2|descriptor_index,指向常量池中的#05(#5 = Utf8 I),I代表基本类型int(2.5.1.3 描述符标志含义).
120h行b列,120h行c列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,
那就可以下一个field_info了:
第二个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
120h行d列,120h行e列 即00 01为u2|access_flags,说明为private类型(根据2.5.1.2 字段访问标志).
120h行f列,130h行0列 即00 06为u2|name_index1,变量名称指向常量池中的#06,(#6 = Utf8 b)
130h行1列,130h行2列 即00 07为u2|descriptor_index,指向常量池中的#07(#7 = Utf8 B),B代表byte.(2.5.1.3 描述符标志含义).
130h行3列,130h行4列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,
那就可以下一个field_info了:
第三个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
130h行5列,130h行6列 即00 09为u2|access_flags,00 09=0x00 01 & 0x00 08==>public static根据2.5.1.2 字段访问标志).
130h行7列,130h行8列 即00 08为u2|name_index1,变量名称指向常量池中的#08,(#8 = Utf8 obj)
130h行9列,130h行a列 即00 09为u2|descriptor_index,指向常量池中的#09(#9 = Utf8 Ljava/lang/Object;),L代表对象类型.(2.5.1.3 描述符标志含义).
130h行b列,130h行c列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,
那就可以下一个field_info了:
第四个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
130d行5列,130h行e列 即00 04为u2|access_flags,s说明为(根据2.5.1.2 字段访问标志).
130h行f列,140h行0列 即00 0A为u2|name_index1,变量名称指向常量池中的#10,(#10 = Utf8 ojbs)
140h行1列,140h行2列 即00 0B为u2|descriptor_index,指向常量池中的#11(#11 = Utf8 [Ljava/lang/Object;),[L代表对象数组类型. (2.5.1.3 描述符标志含义).
140h行3列,140h行4列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,
2.6 文件结构-方法表集合
type | descriptor[number] | remark |
---|---|---|
u2 | methods_count[1] | |
method_info | methods[methods_count] | |
### 2.6.1 概述 | ||
方法表的构造如同字段表一样,依次包括了访问标志(access_flags),名称索引(name_index),描述符索引(descriptor_index),属性表集合(attributes)几项. | ||
#### 2.6.1.1 方法表的结构 | ||
field_info: | ||
类型 | 名称 | 数量 |
— | — | — |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
2.6.1.2 方法访问标志
字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常相似的,都是一个u2的数据类型.
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 方法是否为public |
ACC_PRIVATE | 0x00 02 | 方法是否为private |
ACC_PROTECTED | 0x00 04 | 方法是否为protected |
ACC_STATIC | 0x00 08 | 方法是否为static |
ACC_FINAL | 0x00 10 | 方法是否为final |
ACC_SYHCHRONRIZED | 0x00 20 | 方法是否为synchronized |
ACC_BRIDGE | 0x00 40 | 方法是否是有编译器产生的方法 |
ACC_VARARGS | 0x00 80 | 方法是否接受参数 |
ACC_NATIVE | 0x01 00 | 方法是否为native |
ACC_ABSTRACT | 0x04 00 | 方法是否为abstract |
ACC_STRICTFP | 0x08 00 | 方法是否为strictfp |
ACC_SYNTHETIC | 0x10 00 | 方法是否是有编译器自动产生的 |
方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为"Code"的属性里面,属性表作为calss文件格式中最具扩展的一种数据项目.
在Java语言中,要重载一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名中,因此Java语言里面是无法仅仅靠返回值的不同来堆一个已有方法进行重载的.但是在class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存.也就是说,如果两个方法有相同的名称和特征签名,但是返回值不同,那么也是可以合法共存与同一个class文件中的.
2.6.2 解读
会默认有一个构造方法. 编译的时候就会产生.在解读class的时候就会显示有2个方法.
会发现有:
init 就是init方法. 构造方法
()V 表示方法和viod 构造方法 修饰属性
(II)I ()里面2个是入参类型int,()外的I是返回类型.
源码
package classstruct;
public class HelloWorldMethod {
public int add(int a,int b){
return a+b;
}
private String conact(String str1,Object objStr){
return str1+objStr;
}
}
复制代码
javap结果
D:\workspacejavap -verbose HelloWorldMethod.class
Classfile /D:/workspace/HelloWorldMethod.class
Last modified 2018-1-2; size 827 bytes
MD5 checksum b8942bc40d0567eac771e2620646aee1
Compiled from "HelloWorldMethod.java"
public class classstruct.HelloWorldMethod
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#29 // java/lang/Object."<init>":()V
#2 = Class #30 // java/lang/StringBuilder
#3 = Methodref #2.#29 // java/lang/StringBuilder."<init>":()
V
#4 = Methodref #2.#31 // java/lang/StringBuilder.append:(Lja
va/lang/String;)Ljava/lang/StringBuilder;
#5 = Methodref #2.#32 // java/lang/StringBuilder.append:(Lja
va/lang/Object;)Ljava/lang/StringBuilder;
#6 = Methodref #2.#33 // java/lang/StringBuilder.toString:()
Ljava/lang/String;
#7 = Class #34 // classstruct/HelloWorldMethod
#8 = Class #35 // java/lang/Object
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lclassstruct/HelloWorldMethod;
#16 = Utf8 add
#17 = Utf8 (II)I
#18 = Utf8 a
#19 = Utf8 I
#20 = Utf8 b
#21 = Utf8 conact
#22 = Utf8 (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Stri
ng;
#23 = Utf8 str1
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 objStr
#26 = Utf8 Ljava/lang/Object;
#27 = Utf8 SourceFile
#28 = Utf8 HelloWorldMethod.java
#29 = NameAndType #9:#10 // "<init>":()V
#30 = Utf8 java/lang/StringBuilder
#31 = NameAndType #36:#37 // append:(Ljava/lang/String;)Ljava/la
ng/StringBuilder;
#32 = NameAndType #36:#38 // append:(Ljava/lang/Object;)Ljava/la
ng/StringBuilder;
#33 = NameAndType #39:#40 // toString:()Ljava/lang/String;
#34 = Utf8 classstruct/HelloWorldMethod
#35 = Utf8 java/lang/Object
#36 = Utf8 append
#37 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#38 = Utf8 (Ljava/lang/Object;)Ljava/lang/StringBuilder;
#39 = Utf8 toString
#40 = Utf8 ()Ljava/lang/String;
{
public classstruct.HelloWorldMethod();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lclassstruct/HelloWorldMethod;
public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 this Lclassstruct/HelloWorldMethod;
0 4 1 a I
0 4 2 b I
}
SourceFile: "HelloWorldMethod.java"
复制代码
详细解读过程在之前的基础上继续分析即可.其中方法中包含的属性相关见2.7.
javap中的后面{}包含内容即和方法属性相关信息.
//这个代表2个入参I,出参也是一个I
#17 = Utf8 (II)I
复制代码
2.7 文件结构-属性表集合
type | descriptor[number] | remark |
---|---|---|
u2 | attributes_count[1] | |
attribute_info | attributes[attributes_count] |
2.7.1 概述
在class文件,字段表,方法表都可以携带自己的属性表集合(像前面方法表的时候就用到"code"这个属性表)以用于描述某些场景专有的信息
2.7.1.1 属性表定义的结构
对于每个属性,它的名称需要从常量池中引用一个CONSTANT_utf8_info类型的常量类表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性区说明属性值所占用的位数即可
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u2 | attribute_length | 1 |
u1 | info | attribute_length |
2.7.1.2 虚拟机中预定义的属性
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量池 |
Deprecated | 类,方法,字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClass | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部便狼描述 |
StackMapTable | Code属性 | JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配 |
Signature | 类,方法表,字段表 | 用于支持泛型情况下的方法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | 用于存储额外的调试信息 |
Synthetic | 类,方法表,字段表 | 标志方法或字段为编译器自动生成的 |
LocalVariableTypeTable | 类 | 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
RuntimeVisibleAnnotations | 类,方法表,字段表 | 为动态注解提供支持 |
RuntimeInvisibleAnnotations | 表,方法表,字段表 | 用于指明哪些注解是运行时不可见的 |
RuntimeVisibleParameterAnnotation | 方法表 | 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法 |
RuntimeInvisibleParameterAnnotation | 方法表 | 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数 |
AnnotationDefault | 方法表 | 用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | 用于保存invokeddynamic指令引用的引导方式限定符 |
2.7.1.3 Code属性详解
Java程序方法体中的代码经过Javac编译处理后,最终变为字节码指令存储在Code属性中.Code属性出现在方法表的属性集合中,但是并非所有的方法表都有这个属性.例如接口或类中的方法就不存在Code属性了.
在字节码指令之后的是方法的是方法的显式异常处理表集合,异常表对于Code属性来说并不是必须参在的
结构:
类型 | 名称 | 数量 | 备注 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名称 |
u4 | attribute_length | 1 | 属性长度 |
u2 | max_stack | 1 | 最大栈深度 |
u2 | max_locals | 1 | 局部变量表所需空间 |
u4 | code_length | 1 | |
u1 | code | code_length | |
u2 | exception_table_length | 1 | |
exception_info | exception_table | exception_length | |
u2 | attributes_count | 1 | |
attribute_info | attributes | attributes_count |
2.7.1.4 Exceptions属性详解
Exception属性的作用是列出方法中能抛出的受查异常Check Exceptions,也就是方法描述时在throws关键字之后列举的异常
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u2 | attribute_lrngth | 1 |
u2 | attribute_of_exception | 1 |
u2 | exception_index_tsble | number_of_exceptions |
Exception属性中的number_of_exceptions项表示方法可能抛出的number_of_exceptions种受查异常,每一种受查异常使用一个exception_index_tsble项表示,exception_index_tsble是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型. | ||
#### 2.7.1.5 LineNumberTable属性详解 |
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | line_number_table_length | 1 |
line_number_info | line_number_table | line_number_table_length |
line_number_table是一个数量为line_number_table_length,类型为line_number_info的集合,line_number_info表包括了start_PC和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源代码行号.
LineNumberTable属性用于描述Java源代码行号与字节码行号(字节码偏移量)之间的对应关系。它并不是运行时必须的属性,但默认会生成到Class文件之中,可以在Javac中使用-g:none或-g:lines选项来取消或要求生成这项信息。如果选择不生成LineNumberTable属性表,对程序运行产生的最主要的影响就是在抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候无法按照源码来设置断点
2.7.1.6 总结
虚拟机预定义的属性有20多个,就不意一一介绍,基本上和上述的几个属性差不多.理解含义后,主要通过工具来解读了.
javap 的{}内容为方法和属性的解读.