看懂java的class文件(字节码)(好)

时间:2022-10-03 17:19:40
 

Class文件是一组以8位字节为基础单位的二进制流,包含多个数据项目(数据项目的顺序,占用的字节数均由规范定义),各个数据项目严格按照顺序紧凑的排列在Class文件中,不包含任何分隔符,使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据,没有空隙。当遇到需要占用超过8位字节以上空间的数据项目时,会按照高位在前的方式分割为多个8位字节进行存储

数据项目分为2种基本数据类型(以及由这两种基本数据类型组成的集合):

1,无符号数,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数

2,表,以“_info”结尾,由多个无符号数或其它表构成的复合数据类型

Class文件格式如下表所示:

类型

名称

数量

u4

magic

1

u2

minor_version

1

u2

major_version

1

u2

constant_pool_count

1

cp_info

constant_pool

constant_pool_count-1

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

由于不包含任何分隔符,故表中的数据项,无论是数量还是顺序,都是被严格限定的。哪个字节代表什么含义,长度是多少,先后顺序如何,都不允许改变

使用下面的测试类进行详细说明:

[java]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. package com.test;  
  2.   
  3. public class Test {  
  4.     private int m;  
  5.       
  6.     public int getM(){  
  7.         return m + 1;  
  8.     }  
  9. }  

编译完成的类文件如下:

看懂java的class文件(字节码)(好)

一,魔数(magic)

看懂java的class文件(字节码)(好)

每个Class文件的头四个字节称为魔数,它的唯一作用是用来确定该文件是否为一个能被虚拟机接受的Class文件。使用魔数而不使用文件扩展名是出于安全方面的考虑,因为文件扩展名可以很随意的被改动

magic:魔数,0xCAFEBABE(cafe babe)

二,Class文件版本(minor_version 和 major_version)

看懂java的class文件(字节码)(好)

minor_version:占2字节,次版本号,0x0000

majro_version:占2字节,主版本号,0x0031,转化为十进制为49,是使用JDK1.5编译的(JDK1.5:0x0031,JDK1.6:0x0032,JDK1.7:0x0033)

高版本的JDK可以向下兼容以前版本的Class文件,但是无法运行以后版本的Class文件,即使文件格式并未发生变化

如果使用JDK1.5运行使用JDK1.6编译的Class文件,会报:

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. java.lang.UnsupportedClassVersionError: Bad version number in .class file  

就是由于JDK1.6编译的文件版本号超过了JDK1.5虚拟机所接受的范围

三,常量池(constant_pool_count 和 constant_pool)

看懂java的class文件(字节码)(好)

由于常量池中常量的数目不是固定的,所以在常量池入口首先使用一个2字节长的无符号数constatn_pool_count来代表常量池计数值

constant_pool_count:占2字节,0x0016,转化为十进制为22,即说明常量池中有21个常量(只有常量池的计数是从1开始的,其它集合类型均从0开始),索引值为1~22。第0项常量具有特殊意义,如果某些指向常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可以将索引值置为0来表示

constant_pool:表类型数据集合,即常量池中每一项常量都是一个表,共有11种结构各不相同的表结构数据。这11种表都有一个共同的特点,即均由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型,常量类型及其数据结构如下表所示:

类型

简介

项目

类型

描述

CONSTANT_Utf8_info

utf-8缩略编码字符串

tag

u1

值为1

length

u2

utf-8缩略编码字符串占用字节数

bytes

u1

长度为length的utf-8缩略编码字符串

CONSTANT_Integer_info

整形字面量

tag

u1

值为3

bytes

u4

按照高位在前储存的int值

CONSTANT_Float_info

浮点型字面量

tag

u1

值为4

bytes

u4

按照高位在前储存的float值

CONSTANT_Long_info

长整型字面量

tag

u1

值为5

bytes

u8

按照高位在前储存的long值

CONSTANT_Double_info

双精度浮点型字面量

tag

u1

值为6

bytes

u8

按照高位在前储存的double值

CONSTANT_Class_info

类或接口的符号引用

tag

u1

值为7

index

u2

指向全限定名(参见备注一)常量项的索引

CONSTANT_String_info

字符串类型字面量

tag

u1

值为8

index

u2

指向字符串字面量的索引

CONSTANT_Fieldref_info

字段的符号引用

tag

u1

值为9

index

u2

指向声明字段的类或接口描述符CONSTANT_Class_info的索引项

index

u2

指向字段描述符CONSTANT_NameAndType_info的索引项

CONSTANT_Methodref_info

类中方法的符号引用

tag

u1

值为10

index

u2

指向声明方法的类描述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType_info的索引项

CONSTANT_InterfaceMethodref_info

接口中方法的符号引用

tag

u1

值为11

index

u2

指向声明方法的接口描述符CONSTANT_Class_info的索引项

index

u2

指向名称及类型描述符CONSTANT_NameAndType_info的索引项

CONSTANT_NameAndType_info

字段或方法的部分符号引用

tag

u1

值为12

index

u2

指向该字段或方法名称常量项的索引

index

u2

指向该字段或方法描述符常量项的索引

 

首先来看常量池中的第一项常量,其标志位为0x07,是一个CONSTANT_Class_info类型常量,此类型常量代表一个类或接口的符号引用。根据其数据结构,接下来2位字节用来保存一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型的常量,此常量代表了这个类或接口的全限定名,索引值为0x0002,即指向了常量池中的第二项常量。

第二项常量标志位为0x01,确实是一个CONSTANT_Utf8_info类型的常量。根据其数据结构,接下来2个字节用来保存utf-8缩略编码字符串长度,其值为0x000D,转化为十进制为13,即接下来的13个字节为一个utf-8缩略编码的字符串,为com/test/Test,可以看到正好是测试类的全限定名

由于Class文件中,类的全限定名、字段、方法都是使用CONSTANT_Utf8_info类型常量来描述名称,而该常量的长度由2个字节表示,所以类的全限定名、字段名、方法名的最大长度不能超过2个字节所能表示的最大整数,也就是65535

逐个分析是比较麻烦的,可以使用JDK自带的用于分析Class文件字节码的工具javap(在JDK的bin目录下):

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. d:\>javap -verbose Test  
  2. Compiled from "Test.java"  
  3. public class com.test.Test extends java.lang.Object  
  4.   SourceFile: "Test.java"  
  5.   minor version: 0  
  6.   major version: 49  
  7.   Constant pool:  
  8. const #1 = class        #2;     //  com/test/Test   
  9. const #2 = Asciz        com/test/Test;  
  10. const #3 = class        #4;     //  java/lang/Object   
  11. const #4 = Asciz        java/lang/Object;  
  12. const #5 = Asciz        m;  
  13. const #6 = Asciz        I;  
  14. const #7 = Asciz        <init>;  
  15. const #8 = Asciz        ()V;  
  16. const #9 = Asciz        Code;  
  17. const #10 = Method      #3.#11; //  java/lang/Object."<init>":()V  
  18. const #11 = NameAndType #7:#8;//  "<init>":()V  
  19. const #12 = Asciz       LineNumberTable;  
  20. const #13 = Asciz       LocalVariableTable;  
  21. const #14 = Asciz       this;  
  22. const #15 = Asciz       Lcom/test/Test;;  
  23. const #16 = Asciz       getM;  
  24. const #17 = Asciz       ()I;  
  25. const #18 = Field       #1.#19; //  com/test/Test.m:I  
  26. const #19 = NameAndType #5:#6;//  m:I  
  27. const #20 = Asciz       SourceFile;  
  28. const #21 = Asciz       Test.java;  
  29.   
  30. {  
  31. public com.test.Test();  
  32. ......  

省略了显示结果的后半部分,这里可以看到总共有21个常量,并且可以看到常量的类型,如果常量中保存的为索引值(#),也会提示索引指向常量的具体内容(//后的内容),当然其中也包含了很多特殊的符号(如:()V),这些将会在后面的“六,字段表集合”与“七,方法表集合”中进行说明

四,访问标志(access_flags)

看懂java的class文件(字节码)(好)

接下来2个字节代表访问标志位。这个标志用于识别类或接口层次的访问信息,如:这个Class是类还是接口,是否定义为public类型,是否定义为abstract类型等

标志名称

标志值

含义

ACC_PUBLIC

0x0001

是否为public类型

ACC_FINAL

0x0010

是否被声明为final,只有类可设置

ACC_SUPER

0x0020

是否允许使用invokespecial字节码指令,JDK1.2以后编译出来的类这个标志为真

ACC_INTERFACE

0x0200

标识这是一个接口

ACC_ABSTRACT

0x0400

是否为abstract类型,对于接口和抽象类,此标志为真,其它类为假

ACC_SYNTHETIC

0x1000

标识别这个类并非由用户代码产生

ACC_ANNOTATION

0x2000

标识这是一个注解

ACC_ENUM

0x4000

标识这是一个枚举

根据上面的表格,测试类的访问标志为ACC_PUBLIC | ACC_SUPER = 0x0001 | 0x0020 =1 | 32 = [00000000][00000001] | [00000000][00010000] = [00000000][00010001] = 33 = 0x0021

五,类索引、父类索引与接口索引集合(this_class 、 super_class 、 interfaces_count 和 interfaces)

看懂java的class文件(字节码)(好)

Class文件中由这3项数据来确定这个类的继承关系

this_class:类索引,用于确定这个类的全限定名,占2字节

super_class:父类索引,用于确定这个类父类的全限定名(Java语言不允许多重继承,故父类索引只有一个。除了java.lang.Object类之外所有类都有父类,故除了java.lang.Object类之外,所有类该字段值都不为0),占2字节

interfaces_count:接口索引计数器,占2字节。如果该类没有实现任何接口,则该计数器值为0,并且后面的接口的索引集合将不占用任何字节,

interfaces:接口索引集合,一组u2类型数据的集合。用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中

this_class、super_class与interfaces中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量,通过这个常量中保存的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

this_class的值为0x0001,即常量池中第一个常量,super_class的值为0x0003,即常量池中的第三个常量,interfaces_counts的值为0x0000,故接口索引集合大小为0

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. const #1 = class        #2;     //  com/test/Test   
  2. const #2 = Asciz        com/test/Test;  
  3. const #3 = class        #4;     //  java/lang/Object   
  4. const #4 = Asciz        java/lang/Object;  

可以看到测试类的全限定名为"com/test/Test;",测试类的父类的全限定名为"java/lang/Object;"

六,字段表集合(fields_count 和 fields)

看懂java的class文件(字节码)(好)

fields_count:字段表计数器,即字段表集合中的字段表数据个数。占2字节,其值为0x0001,即只有一个字段表数据,也就是测试类中只包含一个变量(不算方法内部变量)

fields:字段表集合,一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的变量

在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(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中,占2字节,其值为0x0002,可见这个字段由private修饰,与访问标志位十分相似

标志名称

标志值

含义

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

当然实际上,ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED这3个标志只能选择一个,接口中的字段必须有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志,Class文件对此并无规定,这些都是java语言所要求的

name_index代表字段的简单名称(参见备注二),占2字节,是一个对常量池的引用 ,其值为0x0005,即常量池中第5个常量

descriptor_index代表代表参数的描述符(参见备注三),占2个字节,是一个对常量池的引用,其值为0x0006,即常量池中第6个常量

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. const #5 = Asciz        m;  
  2. const #6 = Asciz        I;  

综上,可以推断出源代码定义的字段为(和测试类完全一样):

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. private int m;  

字段表包含的固定数据项到descriptor_index结束,之后跟随一个属性表集合用于存储一些附加信息:attributes_count(属性计数器,占2字节,0x0000,所以该字段没有额外需要描述的信息)和attributes(属性表集合,详细说明见后面“八,属性表集合”)

字段表集合中不会列出从父类或父接口中继承的字段,但是可能列出原本Java代码之中不存在的字段,如:内部类为了保持对外部类的访问性,自动添加指向外部类实例的字段

Java语言中字段是不能重载的,2个字段无论数据类型、修饰符是否相同,都不能使用相同的名称;但是对于字节码,只要字段描述符不同,字段重名就是合法的

七,方法表集合(methods_count 和 methods)

看懂java的class文件(字节码)(好)

methods_count:方法表计数器,即方法表集合中的方法表数据个数。占2字节,其值为0x0002,即测试类中有2个方法

methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样:

类型

名称

数量

u2

access_flags

1

u2

name_index

1

u2

descriptor_index

1

u2

attributes_count

1

attribute_info

attributes

attributes_count

数据项的含义非常相似,仅在访问标志位和属性表集合中的可选项上有略微不同

由于ACC_VOLATILE标志和ACC_TRANSIENT标志不能修饰方法,所以access_flags中不包含这两项,同时增加ACC_SYNCHRONIZED标志、ACC_NATIVE标志、ACC_STRICTFP标志和ACC_ABSTRACT标志

标志名称

标志值

含义

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

0x0100

字段是否为native

ACC_ABSTRACT

0x0400

字段是否为abstract

ACC_STRICTFP

0x0800

字段是否为strictfp

ACC_SYNTHETIC

0x1000

字段是否为编译器自动产生

 

第一个方法(由编译器自动添加的默认构造方法):

看懂java的class文件(字节码)(好)

access_flags为0x0001,即public;name_index为0x0007,即常量池中第7个常量;descriptor_index为0x0008,即常量池中第8个常量

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. const #7 = Asciz        <init>;  
  2. const #8 = Asciz        ()V;  
看懂java的class文件(字节码)(好)

接下来2个字节为属性计数器,其值为0x0001,说明这个方法的属性表集合中有一个属性(详细说明见后面“八,属性表集合”),属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x0000002F,表示Code属性值的字节长度为47。接下来2位为0x0001,表示该方法的操作数栈的深度最大值为1。接下来2位依然为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000005,则紧接着的5个字节0x2AB7000AB1为该方法编译后生成的字节码指令(各字节对应的指令不介绍了,可查询虚拟机字节码指令表)。接下来2个字节为0x0000,说明Code属性异常表集合为空。

看懂java的class文件(字节码)(好)

接下来2个字节为0x0002,说明Code属性带有2个属性,那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。接下来4位为0x00000006,表示LineNumberTable属性值所占字节长度为6。接下来2位为0x0001,即该line_number_table中只有一个line_number_info表,start_pc为0x0000,line_number为0x0003,LineNumberTable属性结束。

看懂java的class文件(字节码)(好)

接下来2位0x000D为Code属性第二个属性的属性名,指向常量池中第13个常量:LocalVariableTable。该属性值所占的字节长度为0x0000000C=12。接下来2位为0x0001,说明local_variable_table中只有一个local_variable_info表,按照local_variable_info表结构,start_pc为0x0000,length为0x0005,name_index为0x000E,指向常量池中第14个常量:this,descriptor_index为0x000F,指向常量池中第15个常量:Lcom/test/Test;,index为0x0000。第一个方法结束

第二个方法:

看懂java的class文件(字节码)(好)

access_flags为0x0001,即public;name_index为0x0010,即常量池中第16个常量;descriptor_index为0x0011,即常量池中第17个常量

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. const #16 = Asciz       getM;  
  2. const #17 = Asciz       ()I;  
看懂java的class文件(字节码)(好)

接下来2个字节为属性计数器,其值为0x0001,说明这个方法有一个方法属性,属性名称为接下来2位0x0009,指向常量池中第9个常量:Code。接下来4位为0x00000031,表示Code属性值的字节长度为49。接下来2位为0x0002,表示该方法的操作数栈的深度最大值为2。接下来2位为0x0001,表示该方法的局部变量占用空间为1。接下来4位为0x0000007,则紧接着的7个字节0x2AB400120460AC为该方法编译后生成的字节码指令。接下来2个字节为0x0000,说明Code属性异常表集合为空。

看懂java的class文件(字节码)(好)

接下来2个字节为0x0002,说明Code属性带有2个属性,那么接下来2位0x000C即为Code属性第一个属性的属性名称,指向常量池中第12个常量:LineNumberTable。接下来4位为0x00000006,表示LineNumberTable属性值所占字节长度为6。接下来2位为0x0001,即该line_number_table中只有一个line_number_info表,start_pc为0x0000,line_number为0x0007,LineNumberTable属性结束。

看懂java的class文件(字节码)(好)

和第一个方法的LocalVariableTable属性基本相同,唯一的区别是局部变量this的作用范围覆盖的长度为7而不是5,第二个方法结束

如果子类没有重写父类的方法,方法表集合中就不会出现父类方法的信息;有可能会出现由编译器自动添加的方法(如:<init>,实例类构造器)

在Java语言中,重载一个方法除了要求和原方法拥有相同的简单名称外,还要求必须拥有一个与原方法不同的特征签名(方法参数集合),由于特征签名不包含返回值,故Java语言中不能仅仅依靠返回值的不同对一个已有的方法重载;但是在Class文件格式中,特征签名即为方法描述符,只要是描述符不完全相同的2个方法也可以合法共存,即2个除了返回值不同之外完全相同的方法在Class文件中也可以合法共存

javap工具在后半部分会列出分析完成的方法(可以看到和我们的分析结果是一样的):

[plain]  view plain copy 看懂java的class文件(字节码)(好) 看懂java的class文件(字节码)(好)
  1. d:\>javap -verbose Test  
  2. ......  
  3. {  
  4. public com.test.Test();  
  5.   Code:  
  6.    Stack=1, Locals=1, Args_size=1  
  7.    0:   aload_0  
  8.    1:   invokespecial   #10; //Method java/lang/Object."<init>":()V  
  9.    4:   return  
  10.   LineNumberTable:  
  11.    line 3: 0  
  12.   
  13.   LocalVariableTable:  
  14.    Start  Length  Slot  Name   Signature  
  15.    0      5      0    this       Lcom/test/Test;  
  16.   
  17. public int getM();  
  18.   Code:  
  19.    Stack=2, Locals=1, Args_size=1  
  20.    0:   aload_0  
  21.    1:   getfield        #18; //Field m:I  
  22.    4:   iconst_1  
  23.    5:   iadd  
  24.    6:   ireturn  
  25.   LineNumberTable:  
  26.    line 7: 0  
  27.   
  28.   LocalVariableTable:  
  29.    Start  Length  Slot  Name   Signature  
  30.    0      7      0    this       Lcom/test/Test;  
  31. }  

 

八,属性表集合(attributes_count 和 attributes)

在Class文件、属性表、方法表中都可以包含自己的属性表集合,用于描述某些场景的专有信息

与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性:

属性名称

使用位置

含义

Code

方法表

Java代码编译成的字节码指令

ConstantValue

字段表

final关键字定义的常量值

Deprecated

类文件、字段表、方法表

被声明为deprecated的方法和字段

Exceptions

方法表

方法抛出的异常

InnerClasses

类文件

内部类列表

LineNumberTale

Code属性

Java源码的行号与字节码指令的对应关系

LocalVariableTable

Code属性

方法的局部变量描述

SourceFile

类文件

源文件名称

Synthetic

类文件、方法表、字段表

标识方法或字段是由编译器自动生成的

每种属性均有各自的表结构。这9种表结构有一个共同的特点,即均由一个u2类型的属性名称开始,可以通过这个属性名称来判段属性的类型

Code属性:Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在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_table_length

u2

attributes_count

1

attribute_info

attributes

attributes_count

max_stack:操作数栈深度最大值,在方法执行的任何时刻,操作数栈深度都不会超过这个值。虚拟机运行时根据这个值来分配栈帧的操作数栈深度

max_locals:局部变量表所需存储空间,单位为Slot(参见备注四)。并不是所有局部变量占用的Slot之和,当一个局部变量的生命周期结束后,其所占用的Slot将分配给其它依然存活的局部变量使用,按此方式计算出方法运行时局部变量表所需的存储空间

code_length和code:用来存放Java源程序编译后生成的字节码指令。code_length代表字节码长度,code是用于存储字节码指令的一系列字节流。

每一个指令是一个u1类型的单字节,当虚拟机读到code中的一个字节码(一个字节能表示256种指令,Java虚拟机规范定义了其中约200个编码对应的指令),就可以判断出该字节码代表的指令,指令后面是否带有参数,参数该如何解释,虽然code_length占4个字节,但是Java虚拟机规范中限制一个方法不能超过65535条字节码指令,如果超过,Javac将拒绝编译

ConstantValue属性:通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量(类变量)才可以使用这项属性。其结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

constantvalue_index

1

可以看出ConstantValue属性是一个定长属性,其中attribute_length的值固定为0x00000002,constantvalue_index为一常量池字面量类型常量索引(Class文件格式的常量类型中只有与基本类型和字符串类型相对应的字面量常量,所以ConstantValue属性只支持基本类型和字符串类型)

对非static类型变量(实例变量,如:int a = 123;)的赋值是在实例构造器<init>方法中进行的

对类变量(如:static int a = 123;)的赋值有2种选择,在类构造器<clinit>方法中或使用ConstantValue属性。当前Javac编译器的选择是:如果变量同时被static和final修饰(虚拟机规范只要求有ConstantValue属性的字段必须设置ACC_STATIC标志,对final关键字的要求是Javac编译器自己加入的要求),并且该变量的数据类型为基本类型或字符串类型,就生成ConstantValue属性进行初始化;否则在类构造器<clinit>方法中进行初始化

Exceptions属性:列举出方法中可能抛出的受查异常(即方法描述时throws关键字后列出的异常),与Code属性平级,与Code属性包含的异常表不同,其结构为:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_exceptions

1

u2

exception_index_table

number_of_exceptions

number_of_exceptions表示可能抛出number_of_exceptions种受查异常

exception_index_table为异常索引集合,一组u2类型exception_index的集合,每一个exception_index为一个指向常量池中一CONSTANT_Class_info型常量的索引,代表该受查异常的类型

InnerClasses属性:该属性用于记录内部类和宿主类之间的关系。如果一个类中定义了内部类,编译器将会为这个类与这个类包含的内部类生成InnerClasses属性,结构为:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

number_of_classes

1

inner_classes_info

inner_classes

number_of_classes

inner_classes为内部类表集合,一组内部类表类型数据的集合,number_of_classes即为集合中内部类表类型数据的个数

每一个内部类的信息都由一个inner_classes_info表来描述,inner_classes_info表结构如下:

类型

名称

数量

u2

inner_class_info_index

1

u2

outer_class_info_index

1

u2

inner_name_index

1

u2

inner_name_access_flags

1

inner_class_info_index和outer_class_info_index指向常量池中CONSTANT_Class_info类型常量索引,该CONSTANT_Class_info类型常量指向常量池中CONSTANT_Utf8_info类型常量,分别为内部类的全限定名和宿主类的全限定名

inner_name_index指向常量池中CONSTANT_Utf8_info类型常量的索引,为内部类名称,如果为匿名内部类,则该值为0

inner_name_access_flags类似于access_flags,是内部类的访问标志

标志名称

标志值

含义

ACC_PUBLIC

0x0001

内部类是否为public

ACC_PRIVATE

0x0002

内部类是否为private

ACC_PROTECTED

0x0004

内部类是否为protected

ACC_STATIC

0x0008

内部类是否为static

ACC_FINAL

0x0010

内部类是否为final

ACC_INTERFACE

0x0020

内部类是否为一个接口

ACC_ABSTRACT

0x0400

内部类是否为abstract

ACC_SYNTHETIC

0x1000

内部类是否为编译器自动产生

ACC_ANNOTATION

0x4000

内部类是否是一个注解

ACC_ENUM

0x4000

内部类是否是一个枚举

LineNumberTale属性:用于描述Java源码的行号与字节码行号之间的对应关系,非运行时必需属性,会默认生成至Class文件中,可以使用Javac的-g:none或-g:lines关闭或要求生成该项属性信息,其结构如下:

类型

名称

数量

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_info类型数据的集合,其所包含的line_number_info类型数据的数量为line_number_table_length,line_number_info结构如下:

类型

名称

数量

说明

u2

start_pc

1

字节码行号

u2

line_number

1

Java源码行号

不生成该属性的最大影响是:1,抛出异常时,堆栈将不会显示出错的行号;2,调试程序时无法按照源码设置断点

LocalVariableTable属性:用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,非运行时必需属性,默认不会生成至Class文件中,可以使用Javac的-g:none或-g:vars关闭或要求生成该项属性信息,其结构如下:

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

local_variable_table_length

1

local_variable_info

local_variable_table

local_variable_table_length

local_variable_table是一组local_variable_info类型数据的集合,其所包含的local_variable_info类型数据的数量为local_variable_table_length,local_variable_info结构如下:

类型

名称

数量

说明

u2

start_pc

1

局部变量的生命周期开始的字节码偏移量

u2

length

1

局部变量作用范围覆盖的长度

u2

name_index

1

指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量名称

u2

descriptor_index

1

指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量描述符

u2

index

1

局部变量在栈帧局部变量表中Slot的位置,如果这个变量的数据类型为64位类型(long或double),

它占用的Slot为index和index+1这2个位置

start_pc + length即为该局部变量在字节码中的作用域范围

不生成该属性的最大影响是:1,当其他人引用这个方法时,所有的参数名称都将丢失,IDE可能会使用诸如arg0、arg1之类的占位符代替原有的参数名称,对代码运行无影响,会给代码的编写带来不便;2,调试时调试器无法根据参数名称从运行上下文中获取参数值

SourceFile属性:用于记录生成这个Class文件的源码文件名称,为可选项,可以使用Javac的-g:none或-g:source关闭或要求生成该项属性信息,其结构如下:

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

u2

sourcefile_index

1

可以看出SourceFile属性是一个定长属性,sourcefile_index是指向常量池中一CONSTANT_Utf8_info类型常量的索引,常量的值为源码文件的文件名

对大多数文件,类名和文件名是一致的,少数特殊类除外(如:内部类),此时如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名

Deprecated属性和Synthetic属性:这两个属性都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念

Deprecated属性表示某个类、字段或方法已经被程序作者定为不再推荐使用,可在代码中使用@Deprecated注解进行设置

Synthetic属性表示该字段或方法不是由Java源码直接产生的,而是由编译器自行添加的(当然也可设置访问标志中的ACC_SYNTHETIC标志,所有由非用户代码产生的类、方法和字段都应当至少设置Synthetic属性和ACC_SYNTHETIC标志位中的一项,唯一的例外是实例构造器<init>和类构造器<clinit>方法)

这两项属性的结构为(当然attribute_length的值必须为0x00000000):

类型

名称

数量

u2

attribute_name_index

1

u4

attribute_length

1

看懂java的class文件(字节码)(好)

起始2位为0x0001,说明有一个类属性。接下来2位为属性的名称,0x0014,指向常量池中第20个常量:SourceFile。接下来4位为0x00000002,说明属性体长度为2字节。最后2个字节为0x0014,指向常量池中第21个常量:Test.java,即这个Class文件的源码文件名为Test.java

PS:

1,全限定名:将类全名中的“.”替换为“/”,为了保证多个连续的全限定名之间不产生混淆,在最后加上“;”表示全限定名结束。例如:"com.test.Test"类的全限定名为"com/test/Test;"

2,简单名称:没有类型和参数修饰的方法或字段名称。例如:"public void add(int a,int b){...}"该方法的简单名称为"add","int a = 123;"该字段的简单名称为"a"

3,描述符:描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)和返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示

标识字符

含义

B

基本类型byte

C

基本类型char

D

基本类型double

F

基本类型float

I

基本类型int

J

基本类型long

S

基本类型short

Z

基本类型boolean

V

特殊类型void

L

对象类型,如:Ljava/lang/Object;

对于数组类型,每一维将使用一个前置的“[”字符来描述,如:"int[]"将被记录为"[I","String[][]"将被记录为"[[Ljava/lang/String;"

用描述符描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组"()"之内,如:方法"String getAll(int id,String name)"的描述符为"(I,Ljava/lang/String;)Ljava/lang/String;"

4,Slot,虚拟机为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型占用1个Slot,64位的数据类型(long和double)占用2个Slot





























 JVM指令集(指令码、助记符、功能描述)

指令码

助记符

功能描述

0x00

nop

无操作

 

0x01

aconst_null

 

指令格式:  aconst_null

 

功能描述:  null进栈。

 

指令执行前

指令执行后

栈底

...

...

 

null

栈顶

 

注意:JVM并没有为null指派一个具体的值。

 

 

0x02

iconst_m1

int型常量值-1进栈

0x03

iconst_0

int型常量值0进栈

0x04

iconst_1

int型常量值1进栈

0x05

iconst_2

int型常量值2进栈

0x06

iconst_3

int型常量值3进栈

0x07

iconst_4

int型常量值4进栈

0x08

iconst_5

int型常量值5进栈

 

0x09

lconst_0

long型常量值0进栈

0x0A

lconst_1

long型常量值1进栈

 

0x0B

fconst_0

float型常量值0进栈

0x0C

fconst_1

float型常量值1进栈

0x0D

fconst_2

float型常量值2进栈

 

0x0E

dconst_0

double型常量值0进栈

0x0F

dconst_1

double型常量值1进栈

 

0x10

bipush

将一个byte型常量值推送至栈顶

0x11

sipush

将一个short型常量值推送至栈顶

 

0x12

ldc

将int、float或String型常量值从常量池中推送至栈顶

0x13

ldc_w

将int、float或String型常量值从常量池中推送至栈顶(宽索引)

0x14

ldc2_w

将long或double型常量值从常量池中推送至栈顶(宽索引)

 

0x15

iload

指定的int型局部变量进栈

0x16

lload

指定的long型局部变量进栈

0x17

fload

指定的float型局部变量进栈

0x18

dload

指定的double型局部变量进栈

0x19

aload

 

指令格式:  aload index

 

功能描述:  当前frame的局部变量数组中下标为

           index的引用型局部变量进栈

 

指令执行前

指令执行后

栈底

...

...

 

objectref

栈顶

 

index  :  无符号一byte整型。和wide指令联用,

           可以使index为两byte。

 

 

0x1A

iload_0

第一个int型局部变量进栈

0x1B

iload_1

第二个int型局部变量进栈

0x1C

iload_2

第三个int型局部变量进栈

0x1D

iload_3

第四个int型局部变量进栈

 

0x1E

lload_0

第一个long型局部变量进栈

0x1F

lload_1

第二个long型局部变量进栈

0x20

lload_2

第三个long型局部变量进栈

0x21

lload_3

第四个long型局部变量进栈

 

0x22

fload_0

第一个float型局部变量进栈

0x23

fload_1

第二个float型局部变量进栈

0x24

fload_2

第三个float型局部变量进栈

0x25

fload_3

第四个float型局部变量进栈

 

0x26

dload_0

第一个double型局部变量进栈

0x27

dload_1

第二个double型局部变量进栈

0x28

dload_2

第三个double型局部变量进栈

0x29

dload_3

第四个double型局部变量进栈

 

0x2A

aload_0

 

指令格式:aload_0

 

该指令的行为类似于aload指令index为0的情况。

 

0x2B

aload_1

 

同上

 

0x2C

aload_2

 

同上

 

0x2D

aload_3

 

同上

 

 

0x2E

iaload

指定的int型数组的指定下标处的值进栈

0x2F

laload

指定的long型数组的指定下标处的值进栈

0x30

faload

指定的float型数组的指定下标处的值进栈

0x31

daload

指定的double型数组的指定下标处的值进栈

0x32

aaload

 

指令格式:  aaload

 

功能描述:  栈顶的数组下标(index)、数组引用

           (arrayref)出栈,并根据这两个数值

           取出对应的数组元素值(value)进栈。

 

抛出异常:  如果arrayref的值为null,会抛出

           NullPointerException。

           如果index造成数组越界,会抛出

           ArrayIndexOutOfBoundsException。

 

指令执行前

指令执行后

栈底

...

...

arrayref

value

index

 

栈顶

 

index      :  int类型

arrayref   :  数组的引用

 

0x33

baload

指定的boolean或byte型数组的指定下标处的值进栈

0x34

caload

指定的char型数组的指定下标处的值进栈

0x35

saload

指定的short型数组的指定下标处的值进栈

 

0x36

istore

将栈顶int型数值存入指定的局部变量

0x37

lstore

将栈顶long型数值存入指定的局部变量

0x38

fstore

将栈顶float型数值存入指定的局部变量

0x39

dstore

将栈顶double型数值存入指定的局部变量

0x3A

astore

 

指令格式:  astore index

 

功能描述:  将栈顶数值(objectref)存入当前

           frame的局部变量数组中指定下标

           (index)处的变量中,栈顶数值出栈。

 

指令执行前

指令执行后

栈底

...

...

objectref

 

栈顶

 

index  :  无符号一byte整数。该指令和wide联

           用,index可以为无符号两byte整数。

 

 

0x3B

istore_0

将栈顶int型数值存入第一个局部变量

0x3C

istore_1

将栈顶int型数值存入第二个局部变量

0x3D

istore_2

将栈顶int型数值存入第三个局部变量

0x3E

istore_3

将栈顶int型数值存入第四个局部变量

 

0x3F

lstore_0

将栈顶long型数值存入第一个局部变量

0x40

lstore_1

将栈顶long型数值存入第二个局部变量

0x41

lstore_2

将栈顶long型数值存入第三个局部变量

0x42

lstore_3

将栈顶long型数值存入第四个局部变量

 

0x43

fstore_0

将栈顶float型数值存入第一个局部变量

0x44

fstore_1

将栈顶float型数值存入第二个局部变量

0x45

fstore_2

将栈顶float型数值存入第三个局部变量

0x46

fstore_3

将栈顶float型数值存入第四个局部变量

 

0x47

dstore_0

将栈顶double型数值存入第一个局部变量

0x48

dstore_1

将栈顶double型数值存入第二个局部变量

0x49

dstore_2

将栈顶double型数值存入第三个局部变量

0x4A

dstore_3

将栈顶double型数值存入第四个局部变量

 

0x4B

astore_0

 

指令格式:  astore_0

 

功能描述:  该指令的行为类似于astore指令index

           为0的情况。

 

0x4C

astore_1

 

同上

 

0x4D

astore_2

 

同上

 

0x4E

astore_3

 

同上

 

 

0x4F

iastore 

将栈顶int型数值存入指定数组的指定下标处

0x50

lastore

将栈顶long型数值存入指定数组的指定下标处

0x51

fastore

将栈顶float型数值存入指定数组的指定下标处

0x52

dastore

将栈顶double型数值存入指定数组的指定下标处

0x53

aastore

 

指令格式:  aastore

 

功能描述:  根据栈顶的引用型数值(value)、数组下

           标(index)、数组引用(arrayref)出

           栈,将数值存入对应的数组元素中。

 

抛出异常:  如果value的类型和arrayref所引用

           的数组的元素类型不兼容,会抛出抛出

           ArrayStoreException。

           如果index造成数组越界,会抛出

           ArrayIndexOutOfBoundsException。

           如果arrayref值为null,会抛出

           NullPointerException。

 

指令执行前

指令执行后

栈底

...

...

arrayref

 

index

 

value

 

栈顶

 

arrayref   :  必须是对数组的引用

index      :  int类型

value      :  引用类型

 

0x54

bastore

将栈顶boolean或byte型数值存入指定数组的指定下标处

0x55

castore

将栈顶char型数值存入指定数组的指定下标处

0x56

sastore

将栈顶short型数值存入指定数组的指定下标处

 

0x57

pop

栈顶数值出栈 (该栈顶数值不能是long或double型)

0x58

pop2

栈顶的一个(如果是long、double型的)或两个(其它类型的)数值出栈

 

0x59

dup

复制栈顶数值,并且复制值进栈

0x5A

dup_x1

复制栈顶数值,并且复制值进栈2次

0x5B

dup_x2

复制栈顶数值,并且复制值进栈2次或3次

0x5C

dup2

复制栈顶一个(long、double型的)或两个(其它类型的)数值,并且复制值进栈

0x5D

dup2_x1

 

0x5E

dup2_x2

 

 

0x5F

swap

栈顶的两个数值互换(要求栈顶的两个数值不能是long或double型的)

 

0x60

iadd

栈顶两int型数值相加,并且结果进栈

0x61

ladd

栈顶两long型数值相加,并且结果进栈

0x62

fadd

栈顶两float型数值相加,并且结果进栈

0x63

dadd

栈顶两double型数值相加,并且结果进栈

 

0x64

isub

栈顶两int型数值相减,并且结果进栈

0x65

lsub

栈顶两long型数值相减,并且结果进栈

0x66

fsub

栈顶两float型数值相减,并且结果进栈

0x67

dsub

栈顶两double型数值相减,并且结果进栈

 

0x68

imul

栈顶两int型数值相乘,并且结果进栈

0x69

lmul

栈顶两long型数值相乘,并且结果进栈

0x6A

fmul

栈顶两float型数值相乘,并且结果进栈

0x6B

dmul

栈顶两double型数值相乘,并且结果进栈

 

0x6C

idiv

栈顶两int型数值相除,并且结果进栈

0x6D

ldiv

栈顶两long型数值相除,并且结果进栈

0x6E

fdiv

栈顶两float型数值相除,并且结果进栈

0x6F

ddiv

栈顶两double型数值相除,并且结果进栈

 

0x70

irem

栈顶两int型数值作取模运算,并且结果进栈

0x71

lrem

栈顶两long型数值作取模运算,并且结果进栈

0x72

frem

栈顶两float型数值作取模运算,并且结果进栈

0x73

drem

栈顶两double型数值作取模运算,并且结果进栈

 

0x74

ineg

栈顶int型数值取负,并且结果进栈

0x75

lneg

栈顶long型数值取负,并且结果进栈

0x76

fneg

栈顶float型数值取负,并且结果进栈

0x77

dneg

栈顶double型数值取负,并且结果进栈

 

0x78

ishl

int型数值左移指定位数,并且结果进栈

0x79

lshl

long型数值左移指定位数,并且结果进栈

 

0x7A

ishr

int型数值带符号右移指定位数,并且结果进栈

0x7B

lshr

long型数值带符号右移指定位数,并且结果进栈

0x7C

iushr

int型数值无符号右移指定位数,并且结果进栈

0x7D

lushr

long型数值无符号右移指定位数,并且结果进栈

 

0x7E

iand

栈顶两int型数值按位与,并且结果进栈

0x7F

land

栈顶两long型数值按位与,并且结果进栈

 

0x80

ior

栈顶两int型数值按位或,并且结果进栈

0x81

lor

栈顶两long型数值按位或,并且结果进栈

 

0x82

ixor

栈顶两int型数值按位异或,并且结果进栈

0x83

lxor

栈顶两long型数值按位异或,并且结果进栈

 

0x84

iinc

指定int型变量增加指定值

 

0x85

i2l

栈顶int值强转long值,并且结果进栈

0x86

i2f

栈顶int值强转float值,并且结果进栈

0x87

i2d

栈顶int值强转double值,并且结果进栈

0x88

l2i

栈顶long值强转int值,并且结果进栈

0x89

l2f

栈顶long值强转float值,并且结果进栈

0x8A

l2d

栈顶long值强转double值,并且结果进栈

0x8B

f2i

栈顶float值强转int值,并且结果进栈

0x8C

f2l

栈顶float值强转long值,并且结果进栈

0x8D

f2d

栈顶float值强转double值,并且结果进栈

0x8E

d2i

栈顶double值强转int值,并且结果进栈

0x8F

d2l

栈顶double值强转long值,并且结果进栈

0x90

d2f

栈顶double值强转float值,并且结果进栈

0x91

i2b

栈顶int值强转byte值,并且结果进栈

0x92

i2c

栈顶int值强转char值,并且结果进栈

0x93

i2s

栈顶int值强转short值,并且结果进栈

 

0x94

lcmp

比较栈顶两long型数值大小,并且结果(1,0,-1)进栈

0x95

fcmpl

比较栈顶两float型数值大小,并且结果(1,0,-1)进栈;当其中一个数值为NaN时, -1进栈

0x96

fcmpg

比较栈顶两float型数值大小,并且结果(1,0,-1)进栈;当其中一个数值为NaN时,1进栈

0x97

dcmpl

比较栈顶两double型数值大小,并且结果(1,0,-1)进栈;当其中一个数值为NaN时,-1进栈

0x98

dcmpg

比较栈顶两double型数值大小,并且结果(1,0,-1)进栈;当其中一个数值为NaN时,1进栈

 

0x99

ifeq

当栈顶int型数值等于0时跳转

0x9A

ifne

当栈顶int型数值不等于0时跳转

0x9B

iflt

当栈顶int型数值小于0时跳转

0x9C

ifge

当栈顶int型数值大于等于0时跳转

0x9D

ifgt

当栈顶int型数值大于0时跳转

0x9E

ifle

当栈顶int型数值小于等于0时跳转

0x9F

if_icmpeq

比较栈顶两int型数值大小,当结果等于0时跳转

0xA0

if_icmpne

比较栈顶两int型数值大小,当结果不等于0时跳转

0xA1

if_icmplt

比较栈顶两int型数值大小,当结果小于0时跳转

0xA2

if_icmpge

比较栈顶两int型数值大小,当结果大于等于0时跳转

0xA3

if_icmpgt

比较栈顶两int型数值大小,当结果大于0时跳转

0xA4

if_icmple

比较栈顶两int型数值大小,当结果小于等于0时跳转

0xA5

if_acmpeq

比较栈顶两引用型数值,当结果相等时跳转

0xA6

if_acmpne

比较栈顶两引用型数值,当结果不相等时跳转

 

0xA7

goto

无条件跳转

 

0xA8

jsr

跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶

0xA9

ret

返回至局部变量指定的index的指令位置(通常与jsr、jsr_w联合使用)

0xAA

tableswitch

用于switch条件跳转,case值连续(可变长度指令)

0xAB

lookupswitch

用于switch条件跳转,case值不连续(可变长度指令)

 

0xAC

ireturn

当前方法返回int

0xAD

lreturn

当前方法返回long

0xAE

freturn

当前方法返回float

0xAF

dreturn

当前方法返回double

0xB0

areturn

 

指令格式:  areturn

 

功能描述:  从方法中返回一个对象的引用。

 

抛出异常:  如果当前方法是synchronized方法,

           并且当前线程不是改方法的锁的拥有者,

           会抛出

           IllegalMonitorStateException。

          

指令执行前

指令执行后

栈底

...

 

objectref

 

栈顶

 

objectref  :  被返回的对象引用。

 

0xB1

return

当前方法返回void

 

0xB2

getstatic

获取指定类的静态域,并将其值压入栈顶

0xB3

putstatic

为指定的类的静态域赋值

0xB4

getfield

获取指定类的实例域,并将其值压入栈顶

0xB5

putfield

为指定的类的实例域赋值

 

0xB6

invokevirtual

调用实例方法

0xB7

invokespecial

调用超类构造方法、实例初始化方法、私有方法

0xB8

invokestatic

调用静态方法

0xb9

invokeinterface

调用接口方法

 

0xBA

---

因为历史原因,该码点为未使用的保留码点

 

0xBB

new

创建一个对象,并且其引用进栈

0xBC

newarray

创建一个基本类型数组,并且其引用进栈

0xBD

anewarray

 

指令格式:  anewarray index1 index2

 

功能描述:  栈顶数值(count)作为数组长度,创建

           一个引用 型数组。栈顶数值出栈,数组引

           用进栈。

 

抛出异常:  如果count小于0,会抛出

           NegativeArraySizeException

 

指令执行前

指令执行后

栈底

...

...

count

arrayref

栈顶

 

count      :  int类型。

arrayref   :  对所创建的数组的引用。

 

0xBE

arraylength

 

指令格式:  arraylength

 

功能描述:  栈顶的数组引用(arrayref)出栈,该

           数组的长度进栈。

 

抛出异常:  如果arrayref的值为null,会抛出

           NullPointerException。

 

指令执行前

指令执行后

栈底

...

...

arrayref

length

栈顶

 

arrayref   :  数组引用

length     :  数组长度

 

 

0xBF

athrow

 

指令格式:  athrow

 

功能描述:  将栈顶的数值作为异常或错误抛出

 

抛出异常:  如果栈顶数值为null,则使用

           NullPointerException代替栈顶数

           值抛出。

           如果方法是的,则有可能抛出

           IllegalMonitorStateException。

 

指令执行前

指令执行后

栈底

...

objectref

objectref

 

栈顶

 

objectref  :  Throwable或其子类的实例的引用。

 

0xC0

checkcast

类型转换检查,如果该检查未通过将会抛出ClassCastException异常

0xc1

instanceof

检查对象是否是指定的类的实例。如果是,1进栈;否则,0进栈

 

0xC2

monitorenter

获得对象锁

0xC3

monitorexit

释放对象锁

 

0xC4

wide

用于修改其他指令的行为

 

0xC5

multianewarray

创建指定类型和维度的多维数组(执行该指令时,栈中必须包含各维度的长度值),并且其引用值进栈

 

0xC6

ifnull

为null时跳转

0xC7

ifnonnull

不为null时跳转

0xC8

goto_w

无条件跳转(宽索引)

0xC9

jsr_w

跳转至指定32位offset位置,并且jsr_w下一条指令地址进栈

 

0xCA

breakpoint

 

 

0xFE

impdep1

 

0xFF

impdep2