博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java二进制指令代码解析
阅读量:6481 次
发布时间:2019-06-23

本文共 37620 字,大约阅读时间需要 125 分钟。

  hot3.png

Java二进制指令代码解析

Java源码在运行之前都要编译成为字节码格式(如.class文件),然后由ClassLoader将字节码载入运行。在字节码文件中,指令代码只是其中的一部分,里面还记录了字节码文件的编译版本、常量池、访问权限、所有成员变量和成员方法等信息(详见Java字节码格式详解)。本文主要简单介绍不同Java指令的功能以及在代码中如何解析二进制指令。 

Java指令是基于栈的体系结构,大部分的指令默认的操作数在栈中。映像中ARM是基于寄存器的操作指令,而x86好像是混合寄存器和存储器的,发现基于栈的操作指令确实简单,学起来很快。不过不知道这种操作的效率怎么样,以我自己的推测应该是不太好的。对这方面不太了解,随便扯几句。 

Java总共有200多条指令,不过很多都是重复的。我的理解,网络是Java一个非常重要的特性,而且Java在设计之初就认为字节码是要在网络中传输的,为了减少网络传输流量,字节码就要尽量设计精简、紧凑。因而Java增加了很多重复指令,比如尽量减少操作数,因而我们会发现Java的很多指令都是没有操作数的;并且指令中的操作数基本上都是当无法将值放到栈中的数据,比如局部变量的索引号和常量池中的索引号。 

还有一点需要注意的是,在运行过程中,所有booleanbytecharshort都是以int类型值存在,因而对这些类型的指令操作很少。然而好像sun实现的虚拟机中。这些类型的数组据说不是以int类型的形式保存的,这个很奇怪。我的理解,以字对齐方式的操作效率会比较高,因而做了这种转换,以空间换时间。 

Java指令集(按功能分类)

常量入栈指令

操作码(助记符)

操作数

描述(栈指操作数栈)

aconst_null

 

null值入栈。

iconst_m1

 

-1(int)值入栈。

iconst_0

 

0(int)值入栈。

iconst_1

 

1(int)值入栈。

iconst_2

 

2(int)值入栈。

iconst_3

 

3(int)值入栈。

iconst_4

 

4(int)值入栈。

iconst_5

 

5(int)值入栈。

lconst_0

 

0(long)值入栈。

lconst_1

 

1(long)值入栈。

fconst_0

 

0(float)值入栈。

fconst_1

 

1(float)值入栈。

fconst_2

 

2(float)值入栈。

dconst_0

 

0(double)值入栈。

dconst_1

 

1(double)值入栈。

bipush

valuebyte

valuebyte值带符号扩展成int值入栈。

sipush

valuebyte1

valuebyte2

(valuebyte1 << 8) | valuebyte2 值带符号扩展成int值入栈。

ldc

indexbyte1

常量池中的常量值(int, float, string reference, object reference)入栈。

ldc_w

indexbyte1

indexbyte2

常量池中常量(int, float, string reference, object reference)入栈。

ldc2_w

indexbyte1

indexbyte2

常量池中常量(long, double)入栈。

 

局部变量值转载到栈中指令

操作码(助记符)

操作数

描述(栈指操作数栈)

(wide)aload

indexbyte

从局部变量indexbyte中装载引用类型值入栈。

aload_0

 

从局部变量0中装载引用类型值入栈。

aload_1

 

从局部变量1中装载引用类型值入栈。

aload_2

 

从局部变量2中装载引用类型值入栈。

aload_3

 

从局部变量3中装载引用类型值入栈。

(wide)iload

indexbyte

从局部变量indexbyte中装载int类型值入栈。

iload_0

 

从局部变量0中装载int类型值入栈。

iload_1

 

从局部变量1中装载int类型值入栈。

iload_2

 

从局部变量2中装载int类型值入栈。

iload_3

 

从局部变量3中装载int类型值入栈。

(wide)lload

indexbyte

从局部变量indexbyte中装载long类型值入栈。

lload_0

 

从局部变量0中装载int类型值入栈。

lload_1

 

从局部变量1中装载int类型值入栈。

lload_2

 

从局部变量2中装载int类型值入栈。

lload_3

 

从局部变量3中装载int类型值入栈。

(wide)fload

indexbyte

从局部变量indexbyte中装载float类型值入栈。

fload_0

 

从局部变量0中装载float类型值入栈。

fload_1

 

从局部变量1中装载float类型值入栈。

fload_2

 

从局部变量2中装载float类型值入栈。

fload_3

 

从局部变量3中装载float类型值入栈。

(wide)dload

indexbyte

从局部变量indexbyte中装载double类型值入栈。

dload_0

 

从局部变量0中装载double类型值入栈。

dload_1

 

从局部变量1中装载double类型值入栈。

dload_2

 

从局部变量2中装载double类型值入栈。

dload_3

 

从局部变量3中装载double类型值入栈。

aaload

 

从引用类型数组中装载指定项的值。

iaload

 

int类型数组中装载指定项的值。

laload

 

long类型数组中装载指定项的值。

faload

 

float类型数组中装载指定项的值。

daload

 

double类型数组中装载指定项的值。

baload

 

boolean类型数组或byte类型数组中装载指定项的值(先转换为int类型值,后压栈)。

caload

 

char类型数组中装载指定项的值(先转换为int类型值,后压栈)。

saload

 

short类型数组中装载指定项的值(先转换为int类型值,后压栈)。

 

将栈顶值保存到局部变量中指令

操作码(助记符)

操作数

描述(栈指操作数栈)

(wide)astore

indexbyte

将栈顶引用类型值保存到局部变量indexbyte中。

astroe_0

 

将栈顶引用类型值保存到局部变量0中。

astore_1

 

将栈顶引用类型值保存到局部变量1中。

astore_2

 

将栈顶引用类型值保存到局部变量2中。

astore_3

 

将栈顶引用类型值保存到局部变量3中。

(wide)istore

indexbyte

将栈顶int类型值保存到局部变量indexbyte中。

istore_0

 

将栈顶int类型值保存到局部变量0中。

istore_1

 

将栈顶int类型值保存到局部变量1中。

istore_2

 

将栈顶int类型值保存到局部变量2中。

istore_3

 

将栈顶int类型值保存到局部变量3中。

(wide)lstore

indexbyte

将栈顶long类型值保存到局部变量indexbyte中。

lstore_0

 

将栈顶long类型值保存到局部变量0中。

lstore_1

 

将栈顶long类型值保存到局部变量1中。

lstore_2

 

将栈顶long类型值保存到局部变量2中。

lstroe_3

 

将栈顶long类型值保存到局部变量3中。

(wide)fstore

indexbyte

将栈顶float类型值保存到局部变量indexbyte中。

fstore_0

 

将栈顶float类型值保存到局部变量0中。

fstore_1

 

将栈顶float类型值保存到局部变量1中。

fstore_2

 

将栈顶float类型值保存到局部变量2中。

fstore_3

 

将栈顶float类型值保存到局部变量3中。

(wide)dstore

indexbyte

将栈顶double类型值保存到局部变量indexbyte中。

dstore_0

 

将栈顶double类型值保存到局部变量0中。

dstore_1

 

将栈顶double类型值保存到局部变量1中。

dstore_2

 

将栈顶double类型值保存到局部变量2中。

dstore_3

 

将栈顶double类型值保存到局部变量3中。

aastore

 

将栈顶引用类型值保存到指定引用类型数组的指定项。

iastore

 

将栈顶int类型值保存到指定int类型数组的指定项。

lastore

 

将栈顶long类型值保存到指定long类型数组的指定项。

fastore

 

将栈顶float类型值保存到指定float类型数组的指定项。

dastore

 

将栈顶double类型值保存到指定double类型数组的指定项。

bastroe

 

将栈顶boolean类型值或byte类型值保存到指定boolean类型数组或byte类型数组的指定项。

castore

 

将栈顶char类型值保存到指定char类型数组的指定项。

sastore

 

将栈顶short类型值保存到指定short类型数组的指定项。

 

wide指令

操作码(助记符)

操作数

描述(栈指操作数栈)

wide

 

使用附加字节扩展局部变量索引(iinc指令特殊)。

 

通用(无类型)栈操作指令

操作码(助记符)

操作数

描述(栈指操作数栈)

nop

 

空操作。

pop

 

从栈顶弹出一个字长的数据。

pop2

 

从栈顶弹出两个字长的数据。

dup

 

复制栈顶一个字长的数据,将复制后的数据压栈。

dup_x1

 

复制栈顶一个字长的数据,弹出栈顶两个字长数据,先将复制后的数据压栈,再将弹出的两个字长数据压栈。

dup_x2

 

复制栈顶一个字长的数据,弹出栈顶三个字长的数据,将复制后的数据压栈,再将弹出的三个字长的数据压栈。

dup2

 

复制栈顶两个字长的数据,将复制后的两个字长的数据压栈。

dup2_x1

 

复制栈顶两个字长的数据,弹出栈顶三个字长的数据,将复制后的两个字长的数据压栈,再将弹出的三个字长的数据压栈。

dup2_x2

 

复制栈顶两个字长的数据,弹出栈顶四个字长的数据,将复制后的两个字长的数据压栈,再将弹出的四个字长的数据压栈。

swap

 

交换栈顶两个字长的数据的位置。Java指令中没有提供以两个字长为单位的交换指令。

 

类型转换指令

操作码(助记符)

操作数

描述(栈指操作数栈)

i2f

 

将栈顶int类型值转换为float类型值。

i2l

 

将栈顶int类型值转换为long类型值。

i2d

 

将栈顶int类型值转换为double类型值。

f2i

 

将栈顶float类型值转换为int类型值。

f2l

 

将栈顶float类型值转换为long类型值。

f2d

 

将栈顶float类型值转换为double类型值。

l2i

 

将栈顶long类型值转换为int类型值。

l2f

 

将栈顶long类型值转换为float类型值。

l2d

 

将栈顶long类型值转换double类型值。

d2i

 

将栈顶double类型值转换为int类型值。

d2f

 

将栈顶double类型值转换为float类型值。

d2l

 

将栈顶double类型值转换为long类型值。

i2b

 

将栈顶int类型值截断成byte类型,后带符号扩展成int类型值入栈。

i2c

 

将栈顶int类型值截断成char类型值,后带符号扩展成int类型值入栈。

i2s

 

将栈顶int类型值截断成short类型值,后带符号扩展成int类型值入栈。

 

整数运算

操作码(助记符)

操作数

描述(栈指操作数栈)

iadd

 

将栈顶两int类型数相加,结果入栈。

isub

 

将栈顶两int类型数相减,结果入栈。

imul

 

将栈顶两int类型数相乘,结果入栈。

idiv

 

将栈顶两int类型数相除,结果入栈。

irem

 

将栈顶两int类型数取模,结果入栈。

ineg

 

将栈顶int类型值取负,结果入栈。

ladd

 

将栈顶两long类型数相加,结果入栈。

lsub

 

将栈顶两long类型数相减,结果入栈。

lmul

 

将栈顶两long类型数相乘,结果入栈。

ldiv

 

将栈顶两long类型数相除,结果入栈。

lrem

 

将栈顶两long类型数取模,结果入栈。

lneg

 

将栈顶long类型值取负,结果入栈。

(wide)iinc

indexbyte

constbyte

将整数值constbyte加到indexbyte指定的int类型的局部变量中。

 

浮点运算

操作码(助记符)

操作数

描述(栈指操作数栈)

fadd

 

将栈顶两float类型数相加,结果入栈。

fsub

 

将栈顶两float类型数相减,结果入栈。

fmul

 

将栈顶两float类型数相乘,结果入栈。

fdiv

 

将栈顶两float类型数相除,结果入栈。

frem

 

将栈顶两float类型数取模,结果入栈。

fneg

 

将栈顶float类型值取反,结果入栈。

dadd

 

将栈顶两double类型数相加,结果入栈。

dsub

 

将栈顶两double类型数相减,结果入栈。

dmul

 

将栈顶两double类型数相乘,结果入栈。

ddiv

 

将栈顶两double类型数相除,结果入栈。

drem

 

将栈顶两double类型数取模,结果入栈。

dneg

 

将栈顶double类型值取负,结果入栈。

 

逻辑运算——移位运算

操作码(助记符)

操作数

描述(栈指操作数栈)

ishl

 

左移int类型值。

lshl

 

左移long类型值。

ishr

 

算术右移int类型值。

lshr

 

算术右移long类型值。

iushr

 

逻辑右移int类型值。

lushr

 

逻辑右移long类型值。

 

逻辑运算——按位布尔运算

操作码(助记符)

操作数

描述(栈指操作数栈)

iand

 

int类型按位与运算。

land

 

long类型的按位与运算。

ior

 

int类型的按位或运算。

lor

 

long类型的按位或运算。

ixor

 

int类型的按位异或运算。

lxor

 

long类型的按位异或运算。

 

控制流指令——条件跳转指令

操作码(助记符)

操作数

描述(栈指操作数栈)

ifeq

branchbyte1

branchbyte2

若栈顶int类型值为0则跳转。

ifne

branchbyte1

branchbyte2

若栈顶int类型值不为0则跳转。

iflt

branchbyte1

branchbyte2

若栈顶int类型值小于0则跳转。

ifle

branchbyte1

branchbyte2

若栈顶int类型值小于等于0则跳转。

ifgt

branchbyte1

branchbyte2

若栈顶int类型值大于0则跳转。

ifge

branchbyte1

branchbyte2

若栈顶int类型值大于等于0则跳转。

if_icmpeq

branchbyte1

branchbyte2

若栈顶两int类型值相等则跳转。

if_icmpne

branchbyte1

branchbyte2

若栈顶两int类型值不相等则跳转。

if_icmplt

branchbyte1

branchbyte2

若栈顶两int类型值前小于后则跳转。

if_icmple

branchbyte1

branchbyte2

若栈顶两int类型值前小于等于后则跳转。

if_icmpgt

branchbyte1

branchbyte2

若栈顶两int类型值前大于后则跳转。

if_icmpge

branchbyte1

branchbyte2

若栈顶两int类型值前大于等于后则跳转。

ifnull

branchbyte1

branchbyte2

若栈顶引用值为null则跳转。

ifnonnull

branchbyte1

branchbyte2

若栈顶引用值不为null则跳转。

if_acmpeq

branchbyte1

branchbyte2

若栈顶两引用类型值相等则跳转。

if_acmpne

branchbyte1

branchbyte2

若栈顶两引用类型值不相等则跳转。

 

控制流指令——比较指令

操作码(助记符)

操作数

描述(栈指操作数栈)

lcmp

 

比较栈顶两long类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈。

fcmpl

 

比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。

fcmpg

 

比较栈顶两float类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。

dcmpl

 

比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。

dcmpg

 

比较栈顶两double类型值,前者大,1入栈;相等,0入栈;后者大,-1入栈;有NaN存在,-1入栈。

 

控制流指令——无条件跳转指令

操作码(助记符)

操作数

描述(栈指操作数栈)

goto

branchbyte1

branchbyte2

无条件跳转到指定位置。

goto_w

branchbyte1

branchbyte2

branchbyte3

branchbyte4

无条件跳转到指定位置(宽索引)。

 

控制流指令——表跳转指令

操作码(助记符)

操作数

描述(栈指操作数栈)

tableswitch

<0-3bytepad>

defaultbyte1

defaultbyte2

defaultbyte3

defaultbyte4

lowbyte1

lowbyte2

lowbyte3

lowbyte4

highbyte1

highbyte2

highbyte3

highbyte4

jump offsets...

通过索引访问跳转表,并跳转。

lookupswitch

<0-3bytepad>

defaultbyte1

defaultbyte2

defaultbyte3

defaultbyte4

npairs1

npairs2

npairs3

npairs4

match offsets

通过键值访问跳转表,并跳转。

 

控制流指令——异常和finally

操作码(助记符)

操作数

描述(栈指操作数栈)

athrow

 

抛出异常。

jsr

branchbyte1

branchbyte2

跳转到子例程序。

jsr_w

branchbyte1

branchbyte2

branchbyte3

branchbyte4

跳转到子例程序(宽索引)。

(wide)ret

indexbyte

返回子例程序。

 

对象操作指令

操作码(助记符)

操作数

描述(栈指操作数栈)

new

indexbyte1

indexbyte2

创建新的对象实例。

checkcast

indexbyte1

indexbyte

类型强转。

instanceof

indexbyte1

indexbyte2

判断类型。

getfield

indexbyte1

indexbyte2

获取对象字段的值。

putfield

indexbyte1

indexbyte2

给对象字段赋值。

getstatic

indexbyte1

indexbyte2

获取静态字段的值。

putstatic

indexbyte1

indexbyte2

给静态字段赋值。

 

数组操作指令

操作码(助记符)

操作数

描述(栈指操作数栈)

newarray

atype

创建type类型的数组。

anewarray

indexbyte1

indexbyte2

创建引用类型的数组。

arraylength

 

获取一维数组的长度。

multianewarray

indexbyte1

indexbyte2

dimension

创建dimension维度的数组。

 

方法调用指令

操作码(助记符)

操作数

描述(栈指操作数栈)

invokespecial

indexbyte1

indexbyte2

编译时方法绑定调用方法。

invokevirtual

indexbyte1

indexbyte2

运行时方法绑定调用方法。

invokestatic

indexbyte1

indexbyte2

调用静态方法。

invokeinterface

indexbyte1

indexbyte2

count

0

调用接口方法。

 

方法返回指令

操作码(助记符)

操作数

描述(栈指操作数栈)

ireturn

 

返回int类型值。

lreturn

 

返回long类型值。

freturn

 

返回float类型值。

dreturn

 

返回double类型值。

areturn

 

返回引用类型值。

return

 

void函数返回。

 

线程同步指令

操作码(助记符)

操作数

描述(栈指操作数栈)

monitorenter

 

进入并获得对象监视器。

monitorexit

 

释放并退出对象监视器。

2.9   methods

methods数组记录了类或接口中的所有方法,包括实例方法、静态方法、实例初始化方法和类初始化方法,但不包括父类或父接口中定义的方法。methods数组中每项都是method_info类型值,它描述了方法的详细信息,如名称、描述符、方法中的attribute(如Code Attribute记录了方法的字节码)等。

method_info

type

descriptor

remark

u2

access_flags

记录方法的访问权限。见2.9.1

u2

name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定方法名称。

u2

descriptor_index

constant_pool中的索引,CONSTANT_Utf8_info类型,指定方法的描述符(见附录C)。

u2

attributes_count

attributes包含的项目数。

attribute_info

attributes[attributes_count]

字段中包含的Attribute集合。见2.9.2-2.9.11

methods数组同样有和fields数组一样的问题,包括fields中项和CONSTANT_Methodref_info以及CONSTANT_InterfaceMethodref_info中的区别。以及设计上的问题。详见field_info中的注。

 

2.9.1    方法访问权限

方法的访问权限

Flag Name

Value

Remarks

ACC_PUBLIC

0x0001

pubilc,包外可访问。

ACC_PRIVATE

0x0002

private,只可在类内访问。

ACC_PROTECTED

0x0004

protected,类内和子类中可访问。

ACC_STATIC

0x0008

static,静态。

ACC_FINAL

0x0010

final,不可被重写。

ACC_SYNCHRONIZED

0x0020

synchronized,同步方法。

ACC_BRIDGE

0x0040

bridge方法,由编译器生成。(什么是bridge方法?)

ACC_VARARGS

0x0080

包含不定参数个数的方法。

ACC_NATIVE

0x0100

native,非Java语言实现的方法。(如何实现Native方法?)

ACC_ABSTRACT

0x0400

abstract,抽象方法。

ACC_STRICT

0x0800

strictfp,设置floating-point模式为FP-strict(什么是FP-strict模式?)

ACC_SYNTHETIC

0x1000

synthetic,由编译器产生,不存在于源代码中。

注:接口中的方法必须同时设置:ACC_PUBLICACC_ABSTRACT。设置了ACC_ABSTRACT后,不可以再设置ACC_FINALACC_STATICACC_PRIVATEACC_NATIVEACC_SYNCHRONIZED ACC_STRICT

 

2.9.2    Code Attribute JVM识别)

每个非abstract、非native方法的attributes集合都包含有且仅有一项Code Attribute。它包含了一个方法的栈、局部变量、字节码以及和代码相关的Attribute信息。

Code Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“Code”)

u4

attribute_length

Attribute内容的字节长度。

u2

max_stack

该方法操作栈的最大深度。

u2

max_locals

该方法调用时需要分配的局部变量的最大个数,包括该方法的参数。

u4

code_length

该方法字节码长度(以字节为单位)

u1

code[code_length]

存放字节码数组(字节码如何解析?)

u2

exception_table_length

异常表的长度。

exception_table_info

每个表项记录一段异常处理代码信息和范围。

u2

start_pc

记录应用该项异常处理的起始字节码。在字节码数组中的起始索引号[start_pc, end_pc)索引号必须是opcode(一条指令的开始位置)对应的位置。

u2

end_pc

u2

handler_pc

记录该项异常处理代码的开始地址。在字节码数组中的开始索引号。索引号必须是opcode对应的位置。

u2

catch_type

constant_pool中的索引,CONSTANT_Class_info类型。指定该项能捕获的异常类(或其子类)。或0用于实现finally语法(即不管什么类型都会捕获。)

exception_table[exception_table_length]

 

u2

attributes_count

attributes包含的项目数。

attribute_info

attributes[attributes_count]

字段中包含的Attribute集合。见2.9.2.1-2.9.2.4

 

2.9.2.1 StackMapTable Attribute JVM识别)

StackMapTable AttributeJ2SE 6中引入,记录了类型检查时需要用到的信息,如字节码的偏移量、局部变量的验证类型、操作栈中的验证类型,用于类型检查过程。在Code Attribute只能包含一项StackMapTable Attribute,记录所有当前Code Attribute中的验证信息。

 

一项StackMapTable Attribute中包含多项stack_map_frame。每项stack_map_frame显式或隐式得记录了字节码的偏移量、局部变量验证类型和操作栈的验证类型。验证器就是通过获取局部变量类型和操作栈类型进行验证的(具体如何验证呢?)

 

stack_map_frame中,并不是直接记录了字节码的索引值,而是记录了offset_delta的值。stack_map_frame中的每一项都通过前一项的值+1+offset_delta计算出当前项对应的真正的字节码的位置,只有当当前stack_map_frame的前一项是当前方法的初始帧(initial frame of the method什么是方法的初始帧?)的时候,offset_delta的值才直接表示字节码位置。

(为什么不直接记录字节码的索引值?为了保证stack_map_frame被正确的排序了。为什么要加1再加offset_delta?为了避免重复出现stack_map_frame项。)

StackMapTable Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“StackMapTable”)

u4

attribute_length

Attribute内容的字节长度。

u2

number_of_entries

stack_map_frame项的数目。

stack_map_frameunion,联合体类型)

每一项显式或隐式得记录了字节码的偏移量、局部变量验证类型和操作栈的验证类型。每一项第一个字节指定当前stack_map_frame的类型(tag

same_frame

same_frame {

     u1 frame_type = SAME; /* 0-63 */

}

tag的值为[0-63]offset_delta = frame_type。表示当前帧和前一帧有相同的局部变量,并且当前操作数栈为空。

same_locals_1_stack

_item_frame

same_locals_1_stack_item_frame {

u1 frame_type = SAME_LOCALS_1_STACK_ITEM; /* 64-127 */

     verification_type_info stack[1]

}

tag值为[64-127]offset_delta = frame_type – 64。表示当前帧和前一帧有相同的局部变量,并且操作栈内的操作数条目数为1,因而它为该操作数栈内的操作数保存了一项verification_type_info(详见下表)。([128-246]tag值保留)

same_locals_1_stack

_item_frame_extended

same_locals_1_stack_item_frame_extended {

     u1 frame_type = SAM_LOCALS_1_STACK_ITEM_EXTENEDED; /* 247 */

     u2 offset_delta;

     verification_type_info stack[1];

}

tag值为247offset_delta = offset_delta。表示当前值帧和前一帧有相同的局部变量,并且操作栈内的操作数条目数为1,因而它为该操作数栈内的操作数保存了一项verification_type_info(详见下表)。和上一类型的区别是这里的offset_delta是直接给出的。

chop_frame

chop_frame {

     u1 frame_type = CHOP; /* 248 – 250 */

     u2 offset_delta;

}

tag值为[248, 250]offset_delta = offset_delta。表示当前操作栈为空,而当前局部变量比前一帧的局部变量少后面的251 – frame_type个局部变量。

same_frame_extended

same_frame_extended {

     u1 frame_type = SAME_FRAME_EXTENDED; /* 251 */

     u2 offset_delta;

}

tag值为251offset_delta = offset_delta。表示当前帧和前一帧有相同的局部变量,并且操作数栈为空。和samep_frame的区别是same_frame_extended中的offset_delta值直接给出。

append_frame

append_frame {

     u1 frame_type = APPEND; /* 252 – 254 */

     u2 offset_delta;

     verification_type_info locals[frame_type – 251];

}

tag值为[252-254]offset_delta = offset_delta。表示操作数栈为空,而当前帧的局部变量比前一帧的局部变量多frame_type – 251个。因而它也定义了frame_type – 251项的verification_type_info类型。详见下表。

full_frame

full_frame {

     u1 frame_type = FULL_FRAME; /* 255 */

     u2 offset_delta;

     u2 number_of_locals;

     verification_type_info locals[number_of_locals];

     u2 number_of_stack_items;

     verification_type_info stack[number_of_stack_items];

}

tag255offset_delta = offset_deltafull_frame则定义了所有的信息,包括offset_delta的值,以及当前帧和前一帧不同的所有局部变量和操作数。locals[0]表示0号局部变量;stack[0]表示栈底操作数。

entries[number_of_entries]

 

 

verification_type_infounion,联合体类型)

记录字节码的验证类型。每一项第一个字节指定验证类型(tag)。

Top_variable_info

Top_variable_info {

    u1 tag = ITEM_Top; /* 0 */

}

指定局部变量的验证类型为Top

Integer_variable_info

Integer_variable_info {

    u1 tag = ITEM_Integer; /* 1 */

}

指定验证类型为int

Float_variable_info

Float_variable_info {

    u1 tag = ITEM_Float; /* 2 */

}

指定验证类型为float

Long_variable_info

Long_variable_info {

    u1 tag = ITEM_Long; /* 4 */

}

指定验证类型为long

Double_variable_info

Double_variable_info {

    u1 tag = ITEM_Double; /* 3 */

}

指定验证类型为double

Null_variable_info

Null_variable_info {

    u1 tag = ITEM_Null; /* 5 */

}

指定验证类型为null

UninitializedThis_variable_info

UninitializedThis_variable_info {

    u1 tag = ITEM_UninitializedThis; /* 6 */

}

指定验证类型为uninitializedThis(什么是uninitializedThis?)。

Object_variable_info

Object_variable_info {

u1 tag = ITEM_Object; /* 7 */

u2 cpool_index; //constant_pool索引,CONSTANT_Class_info类型。

}

指定验证类型为cpool_index中指定的类型实例。

Uninitialized_variable_info

Uninitiated_variable_info {

u1 tag = ITEM_Uninitialized; /* 8 */

u2 offset;

}

指定验证类型为uninitialized(这种类型是指什么?)。offset记录了用于创建实例的new指令的偏移量。(The offset item indicates the offset of the new instruction that created the object being stored in the location.这段话是什么意思?)

注:对验证过程不太了解,因而StackMapTable Attribute的一些描述也没能理解。

 

2.9.2.2 LineNumberTable Attribute (调试信息)

LineNumberTable Attribute用于调试器,以获取某条指令对应的源代码中的行号。多条指令可以对应相同的行号。

LineNumberTable Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“LineNumberTable”)

u4

attribute_length

Attribute内容的字节长度。

u2

line_number_table_length

异常表的长度。

line_number_table

一条指令和源代码行号的映射关系表。

u2

start_pc

一条指令的开始索引(字节码数组中的索引号)

u2

line_number

源代码中的行号。

line_number_table[line_number_table_length]

 

 

2.9.2.3 LocalVariableTable Attribute (调试信息)

LocalVariableTable Attribute用于调试器,以获取在方法运行时局部变量的信息。在一个Code Attribute中只包含10LocalVariableTable Attribute

LocalVariableTable Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“LocalVariableTable”)

u4

attribute_length

Attribute内容的字节长度。

u2

local_variable_table_length

局部变量表的长度。

local_variable_table

每项记录了一个局部变量有值的范围和该局部变量在局部变量数组中的索引值。

u2

start_pc

记录该局部变量的有值范围,字节码数组中的索引范围[start_pc, start_pc+length)

u2

length

u2

name_index

constant_pool中索引,CONSTANT_Utf8_info类型,记录该项代表的局部变量名。

u2

descriptor_index

constant_pool中索引,CONSTANT_Utf8_info类型,记录该项代表的局部变量的字段描述符(见附录C)。

u2

index

记录该项代表的局部变量在方法的局部变量数组中的索引。

local_variable_table[local_variable_table_length]

 

 

2.9.2.4 LocalVariableTypeTable Attribute (调试信息)

LocalVariableTypeTable Attribute用于调试器,以获取在方法运行时泛型局部变量的信息。在一个Code Attribute中只包含10LocalVariableTypeTable Attribute

 

LocalVariableTable AttributeLocalVariableTypeTable Attribute表达的信息是类似的,他们的区别是对泛型类型的局部变量,需要用Signature的形式表达,而不能仅仅用Descriptor的形式表达,因而对泛型类型的局部变量,需要在LocalVariableTable AttributeLocalVariableTypeTable Attribute中同时存在一项;而对非泛型类型的局部变量来说,只要在LocalVariableTable Attribute存在表项就可以了。

 

从这里我们也可以看出泛型是后期才被字节码所支持的痕迹。我感觉很奇怪的是Java在设计的时候,泛型应该已经开始流行了,为什么它在设计之初没有把它考虑进去,而要到后期加入,然后让这种修补的设计做的那么糟糕呢,Java的设计者如果能在设计的时候把它作为扩展考虑,然后再后期去实现,不是更好吗?

LocalVariableTypeTable Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“LocalVariableTypeTable”)

u4

attribute_length

Attribute内容的字节长度。

u2

local_variable_type_table_length

泛型局部变量表的长度。

local_variable_type_table

每项记录了一个泛型局部变量有值的范围和该局部变量在局部变量数组中的索引值。

u2

start_pc

记录该局部变量的有值范围,字节码数组中的索引范围[start_pc, start_pc+length)

u2

length

u2

name_index

constant_pool中索引,CONSTANT_Utf8_info类型,记录该项代表的局部变量名。

u2

signature_index

constant_pool中索引,CONSTANT_Utf8_info类型,记录该项代表的局部变量的字段签名(见附录D)。

u2

index

记录该项代表的局部变量在方法的局部变量数组中的索引。

local_variable_type_table[local_variable_table_length]

 

 

2.9.3    Exceptions Attribute JVM识别)

Exceptions Attribute记录了一个方法需要检验的异常类型。一个method_infoattributes中只能包含一项Exceptions Attribute。即记录一个方法可以抛出的异常类型。

一个方法可以抛出的异常类型遵循三点:

1. 抛出的异常是RuntimeException类型或其子类。

2. 抛出的异常是Error类型或其子类。

3. 抛出的异常是Exceptions Attribute中记录的类型或它们的子类。

Exceptions Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“Exceptions”)

u4

attribute_length

Attribute内容的字节长度。

u2

number_of_exceptions

exception_index_table表项长度

u2

exception_index_table[number_of_exceptions]

每项为constant_pool中的索引,CONSTANT_Class_info类型。记录该方法可抛出的异常类型。

 

2.9.4    RuntimeVisibleParameterAnnotations Attribute

RuntimeVisibleParameterAnnotations Attribute记录该方法在运行时可见的修饰该方法参数的Annotation,从而Java程序可以通过反射机制获取这些Annotation中的值。一个method_info中的attributes中只能包含一项RuntimeVisibleAnnotations Attribute

RuntimeVisibleAnnotations Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“RuntimeVisibleAnnotations”)

u4

attribute_length

Attribute内容的字节长度。

u2

num_parameters

记录该方法中参数个数

parameter_annotations

每个参数和在它之上定义的annotations的映射表。顺序和源码定义顺序一致。

u2

num_annotations

记录该参数定义的annotation的个数

annotation

annotations[num_annotations]

记录该项对应的参数中所有运行时可见的annotaion项,顺序和源码定义顺序一致。annotation类型见附件E

parameter_annotations[num_parameters]

 

注:从该数据结构的定义中可以看到,JavaParameter Annotation存放的信息是很少的,我们只能依赖于定义的顺序来获取这些Annotation,而不能通过参数名或者参数类型来获取相应的Annotation。事实上,

1.       由于参数名的信息只在调试时才有,如LocalVariableTable AttributeLocalVariableTypeTable Attribute中;

2.       而一个方法中不同参数的类型极有可能是相同的;

因而从逻辑上来说,通过参数名或者参数类型返回相应的Annotation信息的方式也是不合理的。由于这个原因,在java.lang.reflect.Method类的方法中也只是给出了:

Annotation[][] getParameterAnnotations()

的方法,获得所有参数中的Annotation,这里的二维数组一维代表参数,一维代表多个Annotation,它们的顺序和源码定义时顺序相同。

2.9.5    RuntimeInvisibleParameterAnnotations Attribute

RuntimeInvisibleParameterAnnotations Attribute记录该方法在运行时不可见的修饰该方法参数的Annotation。一个method_info中的attributes中只能包含一项RuntimeInvisibleAnnotations Attribute

 

RuntimeInvisibleParameterAnotations AttributeRuntimeVisibleAnnotations Attribute区别在于:

后者中的Annotation默认情况下,可以通过Java提供的反射函数获取相应的Annotation,而前者的Annotation在默认情况下是无法通过Java提供的反射函数被获取的,而需要通过特定的机制(如设置JVM的特定参数,该机制由不同的JVM实现来决定)才能通过Java提供的反射函数获取内部的Annotation

 

然而这样就又有一个问题了,RuntimeInvisibleParameterAnotations Attribute是如何被填入值的呢?通过什么机制让源码中方法参数的Annotation是默认不可见的呢?我感觉这个也可能也是由不同编译器提供不同的机制来实现的,不知道sun提供的编译器有没有什么机制支持它了??

 

RuntimeInvisibleAnnotations Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“RuntimeInvisibleAnnotations”)

u4

attribute_length

Attribute内容的字节长度。

u2

num_parameters

记录该方法中参数个数

parameter_annotations

每个参数和在它之上定义的annotations的映射表。顺序和源码定义顺序一致。

u2

num_annotations

记录该参数定义的annotation的个数

annotation

annotations[num_annotations]

记录该项对应的参数中所有运行时不可见的annotaion项,顺序和源码定义顺序一致。annotation类型见附件E

parameter_annotations[num_parameters]

 

 

2.9.6    AnnotationDefault Attribute

AnnotationDefault Attribute用于Annotation类型方法中,以记录该方法所代表的Annotation类型的默认值。每个Annotation类型的method_info中的attributes中只能包含一个AnnotationDefault Attribute项。如:

@Retention(RetentionPolicy.RUNTIME)

@Target({ElementType.METHOD, ElementType.PARAMETER})

public @interface Test {

    public int id() default -1;

    public String description() default "no description";

}

Annotation类产生的class二进制文件中的id方法和description方法的attributes数组中都会包含一项AnnotationDefault Attribute,它们的默认值分别为-1CONSTANT_Integer_info类型)和”no description”CONSTANT_String_info类型)。

AnnotationDefault Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“AnnotationDefault”)

u4

attribute_length

Attribute内容的字节长度。

element_value

default_value

记录该方法表示的Annotation类型的默认值。(element_value结构详见附录E

 

2.9.7    Synthetic Attribute

参见2.11.1

2.9.8    Signature Attribute

参见2.11.2

2.9.9    Deprecated Attribute

参见2.11.3

2.9.10    RuntimeVisibleAnnotations Attribute

参见2.11.4

2.9.11    RuntimeInvisibleAnnotations Attribute

参见2.11.5

 

 

2.10 attributes

attributes数组记录了和类或接口相关的所有Attribute项(和字段相关的Attributefield_infoattributes中,和方法相关的Attributemethod_infoattrubutes中,和字节码相关的AttributeCode Attributeattributes中)。attributes数组中的每项都是attribute_info类型,它描述了Attribute的名称、详细信息等。该attributes数组描述了ClassFile的一些额外信息。JVM必须忽略它不能识别的Attribute,而且那些JVM不能识别的的Attribute也不能影响class文件的语义。

 

当前定义的Attribute有:Code AttributeConstant Value AttibuteDeprecated AttributeEnclosing Method AttributeExceptions AttributeInner Classes AttributeLine Number Table AttributeLocal Variable Table AttributeLocal Variable Type Table AttributeRuntime Visible Annotations AttributeRuntime Invisible Annotation AttributeRuntime Visible Parameter Annotation AttributeRuntime Invisible Parameter Annotation AttributeSignature AttributeSource Debug Extension AttributeSource File AttributeStack Map Table AttributeSynthetic AttributeAnnotation Default Attribute等。它们有些只存在于field_info中,有些只存在method_info中,有些只存在ClassFile中,有些只存在于Code Attribute中,还有些可以同时存在于field_infomethod_infoclassfile中。

 

Attribute结构只存在与ClassFilemethod_infofield_infoCode Attribute结构中。

attribute_infoAttribute的基本数据结构)

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称。

u4

attribute_length

Attribute内容的字节长度。

u1

info[attribute_length]

记录Attribute的内容字节数据。

 

在用户自定义的编译器或者Java虚拟机中可以扩展ClassFile中的Attribute表(即自定义新的Attribute)。但是对自定义的Attribute必须遵循一些规则:

1.       用户自定义的新的Attribute(非sun定义的Attribute)命名必须是遵循Java命名规则,即加入公司的包名信息,如:“com.levin.new_attribute

2.       新增的Attribute只可以作为辅助的信息,如增加和自定义调试器相关的调试信息,但是它们不可以改变ClassFile的语义。什么叫改变ClassFile的语义呢?我现在的理解,比如在自定义的Java虚拟机中,为某个方法新增一个Attribute,用以标记该方法在运行是不可以被调用。不知道这个例子合适不合适。

3.       对于自定义的Java虚拟机,禁止因为某些它不识别的Attribute存在而抛出异常或者直接报错。但是这一层限制可以加载自定义编译器中。Java虚拟机必须忽略它不识别的Attribute

 

以下是定义在ClassFile中的Attribute

2.10.1     InnerClasses Attribute

InnerClasses记录当前类的所有内部类。当前类需要记录的内部类的算法如下:

1. 当前类中定义的内部类,包括方法中定义的类。

2. 如果当前类本身是内部类,则还要记录当前类的外部类,直到外部类不是一个内部类。

如:

class Outer {

    public class Inner {

       public void getMethod() {

           class Inner3 {

              class Inner4 {

              }

           }

       }

       public class Inner2 {

           public class Inner5 {

           }

       }

    }

    public class Inner_1 {

    }

}

Outer中的InnerClasses Attribute

    [inner class info: #17 org/levin/insidejvm/miscs/instructions/Outer$Inner, outer class info: #1 org/levin/insidejvm/miscs/instructions/Outer

     inner name: #19 Inner, accessflags: 1 public],

    [inner class info: #20 org/levin/insidejvm/miscs/instructions/Outer$Inner_1, outer class info: #1 org/levin/insidejvm/miscs/instructions/Outer

     inner name: #22 Inner_1, accessflags: 1 public]

Inner中的InnerClasses Attribute

    [inner class info: #1 org/levin/insidejvm/miscs/instructions/Outer$Inner, outer class info: #23 org/levin/insidejvm/miscs/instructions/Outer

     inner name: #25 Inner, accessflags: 1 public],

    [inner class info: #26 org/levin/insidejvm/miscs/instructions/Outer$Inner$1Inner3, outer class info: #0

     inner name: #28 Inner3, accessflags: 16 final],

    [inner class info: #29 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2, outer class info: #1 org/levin/insidejvm/miscs/instructions/Outer$Inner

     inner name: #31 Inner2, accessflags: 1 public]

Inner5中的InnerClasses Attribute

    [inner class info: #22 org/levin/insidejvm/miscs/instructions/Outer$Inner, outer class info: #24 org/levin/insidejvm/miscs/instructions/Outer

     inner name: #26 Inner, accessflags: 1 public],

    [inner class info: #27 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2, outer class info: #22 org/levin/insidejvm/miscs/instructions/Outer$Inner

     inner name: #29 Inner2, accessflags: 1 public],

    [inner class info: #1 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2$Inner5, outer class info: #27 org/levin/insidejvm/miscs/instructions/Outer$Inner$Inner2

     inner name: #30 Inner5, accessflags: 1 public]

InnerClasses Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“InnerClasses”)

u4

attribute_length

Attribute内容的字节长度。

u2

number_of_classes

记录内部类的数量。

classes

定义每项Inner Class的信息。

u2

inner_class_info_index

constant_pool中的索引,CONSTANT_Class_info类型。记录内部类名。

u2

outer_class_info_index

若当前项内部类不是包含它的类的成员类,则该值为0;否则该值为constant_pool中的索引,CONSTANT_Class_info类型,记录外部类名。

u2

inner_name_index

若当前项的内部类为匿名类,则该值为0;否则该值为constant_pool中的索引,CONSTANT_Utf8_info类型,记录该内部类的简单名(没有前缀)。

u2

inner_class_access_flags

定义当前项的内部类的访问属性。见下表。

classess[number_of_classes]

 

注:为什么需要为内部类保留那么多的信息呢?是为了在反射的时候获取必要的信息或者在反编译的时候可以更好的还原源代码的结构吗?还是有其他的作用?

内部类的访问权限

Flag Name

Value

Remarks

ACC_PUBLIC

0x0001

pubilc,类外可访问。

ACC_PRIVATE

0x0002

private,类内才可访问。

ACC_PROTECTED

0x0004

protected,类和其子类可访问。

ACC_STATIC

0x0008

static,静态内部类。

ACC_FINAL

0x0010

final,不能有子类。

ACC_INTERFACE

0x0200

接口,同时需要设置:ACC_ABSTRACT。不可同时设置:ACC_FINALACC_SUPERACC_Enum

ACC_ABSTRACT

0x0400

抽象类,无法实例化。不可和ACC_FINAL同时设置。

ACC_SYNTHETIC

0x1000

synthetic,由编译器产生,不存在于源代码中。

ACC_ANNOTATION

0x2000

注解类型(annotation),需同时设置:ACC_INTERFACEACC_ABSTRACT

ACC_ENUM

0x4000

枚举类型

 

2.10.2     EnclosingMethod Attribute

当且仅当一个类是匿名类或者本地类(local class),该类才会包含一项且仅有一项EnclosingMethod Attribute

 

然而什么是本地类(local class)呢?我的理解,所谓本地类就是在方法内部定义的类,如以下类的定义:

class A {

    public Iterator getIterator() {

       class LocalClass {

       }

       return new Iterator() {

           public boolean hasNext() { return false; }

           public Object next() { return null; }

           public void remove() { }

       };

    }

}

匿名类A$1.class中的EnclosingMethod Attribute

 Enclosing Method: #29 #31 org/levin/insidejvm/miscs/instructions/A.getIterator()Ljava/util/Iterator;

本地类A$1LocalClass.class中的EnclosingMethod Attribute

 Enclosing Method: #22 #24 org/levin/insidejvm/miscs/instructions/A.getIterator()Ljava/util/Iterator;

 

EnclosingMethod Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“EnclosingMethod”)

u4

attribute_length

Attribute内容的字节长度(4)

u2

class_index

constant_pool中的索引,CONSTANT_Class_info类型。记录定义当前类所在方法的宿主类。如上例中,A$1.classA$1LocalClass.class中的class_index都指向类A

u2

method_index

若当前类没有被方法方法包含,如当前类是赋值给类成员的匿名类,则method_index值为0,否则该method_index的值为constant_pool中的索引,CONSTANT_NameAndType_info类型。记录了class_index指定的类中定义的包含当前类的方法名和类型信息。

注:这里同样也有一个问题,就是EnclosingMethod Attribute存在的目的问题。我现在的理解,该Attribute的存在也应该只是为了用于反射信息和反编译时可以更好的还原原来代码的结构,在虚拟机运行该程序的时候,由于所有的指令已经编译好了,虚拟机应该不需要这些信息。但是事实是这样的吗?有待考证。

 

2.10.3     SourceFile Attribute

SourceFile Attribute用于记录和当前字节码对应的源代码的文件(由编译器产生,该Attribute只是记录相应源代码的文件名,而不记录和路径相关的信息)。一个ClassFile中只能包含一项SourceFile Attribute

SourceFile Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute的名称(“SourceFile”)

u4

attribute_length

Attribute内容的字节长度(2)

u2

sourcefile_index

constant_pool中的索引,CONSTANT_Utf8_info类型。记录相应的源代码文件名。

 

2.10.4     SourceDebugExtension Attribute

SourceDebugExctension AttributeJava为调试时提供的扩展信息,主要用于自定义(扩展)的编译器和调试器。一个ClassFile中只能包含一项SourceFile Attribute

SourceDebugExtension Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“SourceDebugExtension”)

u4

attribute_length

Attribute内容的字节长度。

u1

debug_extension[attribute_length]

Utf-8格式的字符串记录扩展调试信息,不以0结尾。

 

2.11 ClassFilemethod_infofield_info中同时存在的Attribute

2.11.1     Synthetic Attribute

Synthetic Attribute用于指示当前类、接口、方法或字段由编译器生成,而不在源代码中存在(不包含类初始函数和实例初始函数)。相同的功能还有一种方式就是在类、接口、方法或字段的访问权限中设置ACC_SYNTHETIC标记。

 

Synthetic AttributeJDK1.1中引入,以支持内嵌类和接口(nested classes and interfaces)。但是以我现在所知,这些功能都是可以通过ACC_SYNTHETIC标记来表达的,为什么还需要存在Synthetic Attribute呢?在什么样的情况下会生成Synthetic Attribute项呢?我还没有找到,需要继续研究。

Synthetic Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“Synthetic”)

u4

attribute_length

Attribute内容的字节长度(0)。

 

2.11.2     Signature Attribute

Signature Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“Signature”)

u4

attribute_length

Attribute内容的字节长度(2)。

u2

signature_index

constant_pool中的索引,CONSTANT_Utf8_info类型。记录当前类型的签名(类签名、字段签名、方法签名)。

JVM规范中没有指定什么情况下需要生成Signature Attribute。但是从Signature的目的是用于泛型类型,可以推测Signature Attribute存在于当前Signature Attribute所在类型是泛型(泛型类、泛型方法、泛型字段)的时候。它和field_infomethod_infothis_class一起对应于局部变量中的LocalVariableTable AttributeLocalVariableTypeTable Attribute,他们同时都有descriptor版本和signature版本。

 

2.11.3     Deprecated Attribute

Deprecated Attribute指示当前类、方法、字段已经过时了,一些工具,如编译器可以根据该Attribute提示用户他们使用的类、方法、字段已经过时了,最好使用最新版本的类、方法、字段。

Deprecated Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“Deprecated”)

u4

attribute_length

Attribute内容的字节长度(0)。

 

2.11.4     RuntimeVisibleAnnotations Attribute

RuntimeVisibleAnnotations Attribute记录了当前类、方法、字段在源代码中定义的、在运行时可见的AnnotationJava程序可以通过反射函数获取这些Annotation。一个attributes集合中只能包含一项RuntimeVisibleAnnotations Attribute,记录所有运行时可见的Annotation

RuntimeVisibleAnnotations Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“RuntimeVisibleAnnotations”)

u4

attribute_length

Attribute内容的字节长度。

u2

num_annotations

annotations集合长度。

annotation

annotations[num_annotations]

记录所有运行时可见的annotation的集合。annotation类型详见附录E

 

2.11.5     RuntimeInvisibleParameterAnotations Attribute

RuntimeInvisibleAnnotations Attribute记录了当前类、方法、字段在源代码中定义的、在运行时不可见的Annotation。默认情况下,这些Annotation是不可被Java提供的反射函数获取的,需要通过和实现相关的机制来获取这些Annotation。一个attributes集合中只能包含一项RuntimeInvisibleAnnotations Attribute,记录所有运行时不可见的Annotation

RuntimeInvisibleAnnotations Attribute

type

descriptor

remark

u2

attribute_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。指定Attribute名称(“RuntimeInvisibleAnnotations”)

u4

attribute_length

Attribute内容的字节长度。

u2

num_annotations

annotations集合长度。

annotation

annotations[num_annotations]

记录所有运行时不可见的annotation的集合。annotation类型详见附录E

 

 

总体格式

magic(0xCAFEBABE)

version(major.minor)

constant pool

CONSTANT_Utf8_info(1)

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)

access_flags

this_class

super_class

interfaces

fields

access_flags

name

descriptor

attributes

ConstantValue Attribute

Synthetic Attribute

Signature Attribute

Deprecated Attribute

RuntimeVisibleAnnotations Attribute

RuntimeInvisibleAnnotations Attribute

methods

access_flags

name

descriptor

attributes

Code Attribute

StackMapTable Attribute

LineNumberTable Attribute

LocalVariableTable Attribute

LocalVariableTypeTable Attribute

Exceptions Attribute

RuntimeVisibleParameterAnnotations Attribute

RuntimeInvisibleParameterAnnotations Attribute

AnnotationDefault Attribute

Synthetic Attribute

Signature Attribute

Deprecated Attribute

RuntimeVisibleAnnotations Attribute

RuntimeInvisibleAnnotations Attribute

attributes

InnerClasses Attribute

EnclosingMethod Attribute

SourceFile Attribute

SourceDebugExtension Attribute

Synthetic Attribute

Signature Attribute

Deprecated Attribute

RuntimeVisibleAnnotations Attribute

RuntimeInvisibleAnnotations Attribute

 

附件Java字节码中的类和接口名

 

Java字节码中类和接口名主要表现以下几点:

1.       类和接口名都是以全限定名的方式存放(包名加类或接口名)。

2.       在源代码中的点分隔符”.”在字节码中以斜杠”/”代替。如:“java.lang.Object-> java/lang/Object

3.       数组类型名做了特殊处理。如:“int[][]-> [[I”、“Thread[]->[Ljava/lang/Thread”。详见附录BJava字节码中的数组类型名

 

 

附件 Java字节码中的数组类型名

 

Java中,数组被认为是类,因而它也有对应的类名表示,而Java字节码为数组名指定了特定的格式:

1. 所有数组名都以“[”开头n维数组有n个“[”。

2. 对引用类型的数组,在“[”后加“L后加引用类型的全限定名。

3. 对基本类型,在“[”后加基本类型的对应字符

基本类型对应字符表

基本类型

对应字符

byte

B

char

C

double

D

float

F

int

I

long

J

short

S

boolean

Z

 

附件 描述符(Descriptor

 

描述符(Descriptor)定义了字段或方法的类型(descriptor is a string representing the type of a field or method.这段描述感觉不怎么精确)。它存放在constant pool中的CONSTANT_Utf8_info类型项中。

 

1.       字段描述符(Field Descriptor

字段描述符是定义了字段、局部变量、参数等类型的字符串。即附录A中的类或接口名。

语法定义:

FieldDescrptor :

FieldType

 

BaseType  BCDFIJSZ(参考附录B中的基本类型对应字符表)

ObjectType  LfullClassName;

ArrayType  [+BaseType | [+ObjectType

FieldType  BaseType | ObjectType | ArrayType

如:[[D -> double[][][Ljava/lang/Thread; -> Thread[]I->intLjava/lang/Object; -> Object

 

2.       方法描述符(Method Descriptor

方法描述符是定义了方法参数、方法返回等信息的字符串。

语法定义:

MethodDescriptor:

         ParameterDescriptor*ReturnDescriptor

 

ParameterDescriptor  FieldType

ReturnDescriptor  FieldType | VoidDescriptor

VoidDescriptor  V

如:void methodint i, Object obj-> (ILjava/lang/Object;)V

Object getValue()-> ( )Ljava/lang/Object;

Object mymethod(int i, double d, Object o) -> (IDLjava/lang/Object;)Ljava/lang/Object;

 

附件 签名(Signature

 

签名(Signature)定义了类、字段或方法的泛型类型信息(signature is a string representing the generic type of a field or method, or generic type information for a class declaration. 这段描述感觉不怎么精确)。它也存放在constant pool中的CONSTANT_Utf8_info类型项中。

它存在于Signature Attribute中,只有包含泛型的类、字段、方法才会产生Signature Attribute

 

签名信息并不是给JVM用的,而是用于编译、调试、反射。

 

1.       类签名

语法定义:

ClassSignature:

FormalTypeParametersopt SuperclassSignature SuperinterfaceSignature*

 

FormalTypeParameters:

<FormalTypeParameter+>

FormalTypeParameter:

Identifier ClassBound InterfaceBound*

ClassBound:

FieldTypeSignatureopt

InterfaceBound:

FieldTypeSignature

SuperclassSignature:

ClassTypeSignature

SuperinterfaceSignature:

ClassTypeSignature

FieldTypeSignature:

ClassTypeSignature

ArrayTypeSignature

TypeVariableSignature

ClassTypeSignature:

PackageSpecifier* SimpleClassTypeSignature

ClassTypeSignatureSuffix* ;

PackageSpecifier:

Identifier / PackageSpecifier*

SimpleClassTypeSignature:

Identifier TypeArgumentsopt

ClassTypeSignatureSuffix:

SimpleClassTypeSignature

TypeVariableSignature:

T Identifier ;

TypeArguments:

<TypeArgument+>

TypeArgument:

WildcardIndicatoropt FieldTypeSignature

*

WildcardIndicator:

+

-

ArrayTypeSignature:

[TypeSignature

TypeSignature:

FieldTypeSignature

BaseType

以上定义没有看懂??例子如:

class MyClass<T> { } 定义的类,产生如下的签名:

<T:Ljava/lang/Object;>Ljava/lang/Object;

而对以下类定义:

class MyClass<T1, T2> extends ClassFileParser implements IndexParser {

}

则产生如下签名:

<T1:Ljava/lang/Object;T2:Ljava/lang/Object;>Lorg/levin/classfilereader/ClassFileParser;Lorg/levin/classfilereader/IndexParser;

 

2.       字段签名

语法定义如上,没能看懂。从Tomcat代码中的Digester.class文件中可以解析得到如下的例子:

Ljava/util/HashMap<Ljava/lang/String;Ljava/util/Stack<Ljava/lang/String;>;>;(对应的descriptor:“Ljava/util/HashMap;”)

Ljava/util/Stack<Ljava/lang/Object;>;(对应的descriptor:“Ljava/util/Stack;”)

 

3.       方法签名

语法定义:

MethodTypeSignature:

FormalTypeParametersopt (TypeSignature*ReturnType

ThrowsSignature*

ReturnType:

TypeSignature

VoidDescriptor

ThrowsSignature:

^ClassTypeSignature

^TypeVariableSignature

也没能看懂。同样从Tomcat代码中的Digester.class文件中可以解析得到如下例子:

(Ljava/lang/String;Ljava/lang/Class<*>;Ljava/lang/String;)V(对应descriptor:“(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)V”)

(Ljava/lang/String;Ljava/lang/Class<*>;Ljava/lang/String;Z)V(对应descriptor:“(Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;Z)V”)

()Ljava/util/Map<Ljava/lang/String;Ljava/net/URL;>;(对应descriptor:“()Ljava/util/Map;”)

 

附录Eannotation结构和element_value结构

 

1.      annotation结构

每一项annotation结构记录一项用户定义的annotation的值。如:

    @Test(id = 4, description = "description", useCase = @UseCase())

    @UseCase()

    void testExecute(int a) {

    }

编译器会为该方法生成两项annotation。每项annotation指定了annotation的类型和键值对。

annotation结构

type

descriptor

remark

u2

type_index

constant_pool中的索引。CONSTANT_Utf8_info类型。以字段描述符(field descriptor)方式记录当前结构表示的annotation类型。

u2

num_element_value_pairs

记录当前annotation中的键值对数。

element_value_pair

记录每项annotation中的键值对表。

u2

element_name_index

constant_pool中的索引。CONSTANT_Utf8_info类型。记录当前annotation中当前键值对的键名。如上例的“id”、“description”等。

element_value

value

当前annotation中当前键值对的值。详见element_value结构一节。

element_value_pairs[num_element_value_pairs]

 

 

2.      element_value结构

element_value结构记录了所有annotation类型的键值对中的值。它是一个联合类型,可以表示多种类型的值。

element_value结构

type

descriptor

remark

u1

tag

tag记录了当前annotation键值对中值的类型,’B’’C’’D’’F’’I’’J’’S’’Z’表示基本类型(见附录B中的基本类型对应表);其他的合法值有:

’s’ -> String

‘e’ -> enum constant

‘c’ -> class

‘@’ -> annotation type

‘[‘ -> array

value 联合体类型(union

union类型,记录当前annotaion键值对中的值。

u2

constant_value_index

constant_pool中的索引,索引项必须是常量类型。当tag中的值为’B’ ‘C’ ‘D’ ‘F’ ‘I’ ‘J’ ‘S’ ‘Z’ ‘s’时该项有效。

enum_const_value

tag值为’e’时,该项有效。记录枚举类型值。

u2

type_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。记录当前枚举类型二进制名(binary name,好像就是类型名,以descriptor的形式表示)。

u2

const_name_index

constant_pool中的索引,CONSTANT_Utf8_info类型。记录当前枚举类型的值(枚举类型内部成员字符串)。

enum_const_value

u2

class_info_index

constant_pool中的索引,CONSTANT_Utf8_info类型。以descriptor记录当前值所表达的Class类型。当tag值为’c’时,该项有效。

annotation

annotation_value

tag值为’@’时,该项有效。记录当前annotation键值对中的值为内嵌的annotation

array_value

tag值为’[‘时,该项有效。记录当前annotation键值对中的值为数组类型。

u2

num_values

数组的长度。

element_value

values[num_values]

每一项记录数组中的值。

array_value

value

 

注:从这个结构中,我们也可以得出annotation中可以设置的值类型:

1.       基本类型值(bytechardoublefloatintlongshortboolean

2.       字符串(String

3.       枚举(enum

4.       类实例(Class

5.       嵌套注解类型(annotation

6.       以上所有以上类型的一维数组。

转载于:https://my.oschina.net/liting/blog/422654

你可能感兴趣的文章
使用 Docker 作为 Python 开发环境 【已翻译100%】
查看>>
svn服务器搭建和使用(二)
查看>>
使用 HA-LVM 实现高可用存储
查看>>
《BackTrack 5 Cookbook中文版——渗透测试实用技巧荟萃》—第1章1.8节启动网络服务...
查看>>
《Python语言程序设计》—— 导读
查看>>
Zenwalk Linux 220217,基于 Slackware 的 Linux操作系统
查看>>
Magento2 - 能够完全控制网店的漏洞 (CVE-2016-4010)
查看>>
一个 7 岁女孩能告诉你的关于编程的事
查看>>
《Unity着色器和屏幕特效开发秘笈(原书第2版)》——1.4 给着色器添加属性...
查看>>
《Cisco IOS XR技术精要》一2.8 转发路径
查看>>
《数学建模:基于R》一一1.5 列联表检验
查看>>
MaxCompute SQL Row_Sequence 实现列自增长
查看>>
手机大小 Linux 服务器问世,采用电池供电
查看>>
10 件在 PHP 7 中不要做的事情
查看>>
《计算机科学概论(第12版)》—第1章1.9节数据压缩
查看>>
《乐高EV3机器人搭建与编程》一2.3 球头万向轮
查看>>
从源码探究MySQL5.7高吞吐事务量的背后操手
查看>>
Linux 平台七大桌面环境通览
查看>>
dubbo请求调用过程分析
查看>>
《Oracle SQL疑难解析》——1.9 从表中删除不需要的行
查看>>