字节码和类的加载笔记二
2024-1-7 00:2:58 Author: 白帽子(查看原文) 阅读量:9 收藏

字段表集合

用于描述接口或类中声明的变量,字段(field)包括类级变量以及实例变量,说白就是成员变量。

但是不包括方法中或者代码块中声明的变量。

字段叫什么以及定义什么数据类型这些都是无法固定的,只能引用常量池中的常量来描述。

它描述了字段的完整信息,比如字段的标识符,访问修饰符,(public private或protected) 是类变量还是实例变量(static修饰符)

是否是常量(final修饰符)等。这些信息都会在我们的字段表中进行展示。

注意:

字段表中不会列出从父类或者实现的接口中继承而来的字段

字段表计数器

field_count的值表示当前class文件fields表的成员个数,使用两个字节来表示。

fields表中的每个成员都是一个field_info结构,用于表示类或接口所声明的类字段或实例字段,不包含方法内部声明的变量,也不包括从父类或父接口继承的那些字段。

字段表结构

fields表中的每一个成员都必须是field_info结构的数据项,用于表示当前类或者接口某个字段的描述。

总的来说这个fields表,比如说我们有3个字段都存在于fields表中,那么每一个字段都是一个field_info结构。

field_info结构中访问修饰符,可变性(final),并发可变性,是否可以序列化,字段数据类型,字段名称等等。

如下图就是字段表的的结构。

字段访问标识

字段表解析

那么我们上面说过第一组也就是前两个字节表示的是属性计数器,那么我们从第三个和第四个字节开始。

这里的00 02表示当前我们第一个属性的访问标识。我们对照以上表可以看到访问标识ACC_PRIVATE。

我们来看下一组也就是00 08 代表的是字段名索引,只要一提索引我们立马就可以想到要从常量池中查找。

接下来00 09代表的是字段描述符索引。

描述符的作用就是就是用来表达字段的数据类型,方法的参数列表以及返回值。

基本数据类型(int byte double long short)这些基本的数据类型以及没有返回值的void都是用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示。

所以我们找到第九项来进行查看发现是一个大写的I 也就是int类型。

00 00 表示字段计数器。00表示我们当前字段是没有属性的。

方法表集合

在字节码文件中,每一个method_info项都对应着一个类或接口中的方法信息。比如方法的访问修饰符(public private protected),方法的返回值类型以及参数信息等。

如果这个方法不是抽象的或者不是native的那么字节码中会体现出来。

方法表中可能会出现编译器自动添加的方法,最典型的便是编译器产生的方法信息,比如(类(接口))初始化方法<client>和实例初始化方法<init>()。

方法计数器

跟字段表一样,比如我们有三组方法,都放到了方法表中,那么每一个方法都表示是一个method_info,这个method_info中包含方法的修饰符,返回值类型,参数等信息。

方法表结构

Method_info结构可以表示类和接口中定义的所有方法,包括实例方法,类方法,实例初始化方法,类或借口初始化方法。

方法表解析

这里的00 03表示我们的方法计数器,可以看到我们有3个方法。

00 01 表示我们的访问标志,和前面字段表一样 01表示ACC_PUBLIC

00 0A 0A的的十进制为10,00 0A表示的是方法名的索引,看到索引就直接去找他对应的项,也就是第十项。

可以看到是一个<init> 这个代表我们的空参构造器。

00 0B表示描述符索引,0B的十进制是11,所以找到11项,根据我们上面在字段表中说过,没有返回值的void都是用一个大写字符来表示。

00 01表示属性计数器,从 00 01开始后面到00都是属性的信息。

属性表集合

方法表集合之后就是属性表集合,比如该Class文件的源文件信息,以及任何带有RetentionPolicy.CLASS或RetentionPolicy.RUNTIME的注解,这类信息被用于Java虚拟机的验证和运行,以及Java程序的调试。

字段表和方法表中都有自己的属性表,用于描述某些场景转专有的信息。

方法属性表的解析

既然有表那么表中的每一项,都是一个attributes_info结构,属性表比较灵活,各种不同的属性只要满足以下结构即可。

属性的通用格式

00 0C 表示的就是属性名索引, 0C的十进制是12,所以找到12这个索引项,他是一个Code属性。

Code属性就是存放方法体里面的代码,但是并非所有方法都有Code属性,像接口或者抽象方法,他们没有方法体所以就没有Code属性了。

Code属性对照表

接下来00 00 00 38表示我们属性的长度为38。38的十进制是56,也就是说长度为56。如下图就是56长度。

那么这里00 02表示的就是操作栈深度的最大值。

00 0A 0A的十进制是10 00 0A表示局部变量表所需的存续空间

后续跟着表进行对照即可。

Javap指令解析class文件

Javap是jdk自带的反解析工具,它的作用就是根据class字节码文件,反解析出当前类文件的code区(字节码指令),局部变量表,异常表和代码行偏移量映射表 常量池等信息。

在使用javap反解析class文件之前,我们一般编译java文件都使用的是:

javac xxx.java 这样编译的话就不会生成局部变量表的信息,如果你使用javac -g xxx.java 那么就可以生成所有的相关信息了。

idea默认使用的就是javac -g的方式。

Javap的使用

javap <options> <classes>

Javap -public 显示公共类和成员

Javap -protected 显示受保护的/公共类和成员

java -p -private 显示所有类和成员

-package 显示程序包/受保护的/公共类和成员

-sysinfo 显示正在处理的类的系统信息

-constants 显示静态最终常量

-s 输出内部类型签名

-l 输出行号和本地变量表

-c 对代码进行反汇编

-v 输出附加信息 包括行号 本地变量表 反汇编等详细信息

javap解析Class文件
package com.jvm.test;
public class JavaTest { private int num; boolean flag; protected char gender; public String info;
public static final int COUNTS = 1; static{ String url = "www.baidu.com"; } { info = "java"; } public JavaTest(){
} private JavaTest(boolean flag){ this.flag = flag; } private void methodPrivate(){
} int getNum(int i){ return num + i; } protected char showGender(){ return gender; } public void showInfo(){ int i = 10; System.out.println(info + i); }}

使用 javap -v -p JavaTest.class > j.txt 命令输出到文本。

如下分析:

Classfile /Users/relay/Downloads/JVM-demo/Jvm-demo001/src/main/java/com/jvm/test/JavaTest.class //字节码文件所属的路径  Last modified 2023年7月3日; size 1595 bytes 、 //最后修改的时间 以及文件的大小  SHA-256 checksum 87d1a4427f47396b0c64c721cbce3d817443fd35aef5b7563b3f176e51c0f77f //md5散列值  Compiled from "JavaTest.java" //源文件的名称public class com.jvm.test.JavaTest  minor version: 0                      //小版本  major version: 63                     //主版本  flags: (0x0021) ACC_PUBLIC, ACC_SUPER //访问标识  this_class: #10                         // com/jvm/test/JavaTest  super_class: #2                         // java/lang/Object  interfaces: 0, fields: 5, methods: 7, attributes: 3Constant pool:  //常量池   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V   #2 = Class              #4             // java/lang/Object   #3 = NameAndType        #5:#6          // "<init>":()V   #4 = Utf8               java/lang/Object   #5 = Utf8               <init>   #6 = Utf8               ()V   #7 = String             #8             // java   #8 = Utf8               java   #9 = Fieldref           #10.#11        // com/jvm/test/JavaTest.info:Ljava/lang/String;  #10 = Class              #12            // com/jvm/test/JavaTest  #11 = NameAndType        #13:#14        // info:Ljava/lang/String;  #12 = Utf8               com/jvm/test/JavaTest  #13 = Utf8               info  #14 = Utf8               Ljava/lang/String;  #15 = Fieldref           #10.#16        // com/jvm/test/JavaTest.flag:Z  #16 = NameAndType        #17:#18        // flag:Z  #17 = Utf8               flag  #18 = Utf8               Z  #19 = Fieldref           #10.#20        // com/jvm/test/JavaTest.num:I  #20 = NameAndType        #21:#22        // num:I  #21 = Utf8               num  #22 = Utf8               I  #23 = Fieldref           #10.#24        // com/jvm/test/JavaTest.gender:C  #24 = NameAndType        #25:#26        // gender:C  #25 = Utf8               gender  #26 = Utf8               C  #27 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;  #28 = Class              #30            // java/lang/System  #29 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;  #30 = Utf8               java/lang/System  #31 = Utf8               out  #32 = Utf8               Ljava/io/PrintStream;  #33 = InvokeDynamic      #0:#34         // #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;  #34 = NameAndType        #35:#36        // makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;  #35 = Utf8               makeConcatWithConstants  #36 = Utf8               (Ljava/lang/String;I)Ljava/lang/String;  #37 = Methodref          #38.#39        // java/io/PrintStream.println:(Ljava/lang/String;)V  #38 = Class              #40            // java/io/PrintStream  #39 = NameAndType        #41:#42        // println:(Ljava/lang/String;)V  #40 = Utf8               java/io/PrintStream  #41 = Utf8               println  #42 = Utf8               (Ljava/lang/String;)V  #43 = String             #44            // www.baidu.com  #44 = Utf8               www.baidu.com  #45 = Utf8               COUNTS  #46 = Utf8               ConstantValue  #47 = Integer            1  #48 = Utf8               Code  #49 = Utf8               LineNumberTable  #50 = Utf8               LocalVariableTable  #51 = Utf8               this  #52 = Utf8               Lcom/jvm/test/JavaTest;  #53 = Utf8               (Z)V  #54 = Utf8               methodPrivate  #55 = Utf8               getNum  #56 = Utf8               (I)I  #57 = Utf8               i  #58 = Utf8               showGender  #59 = Utf8               ()C  #60 = Utf8               showInfo  #61 = Utf8               <clinit>  #62 = Utf8               SourceFile  #63 = Utf8               JavaTest.java  #64 = Utf8               BootstrapMethods  #65 = MethodHandle       6:#66          // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;  #66 = Methodref          #67.#68        // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;  #67 = Class              #69            // java/lang/invoke/StringConcatFactory  #68 = NameAndType        #35:#70        // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;  #69 = Utf8               java/lang/invoke/StringConcatFactory  #70 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;  #71 = String             #72            // \u0001\u0001  #72 = Utf8               \u0001\u0001  #73 = Utf8               InnerClasses  #74 = Class              #75            // java/lang/invoke/MethodHandles$Lookup  #75 = Utf8               java/lang/invoke/MethodHandles$Lookup  #76 = Class              #77            // java/lang/invoke/MethodHandles  #77 = Utf8               java/lang/invoke/MethodHandles  #78 = Utf8               Lookup{  private int num; //字段表集合信息 字段名    descriptor: I //字段描述符 字段的类型    flags: (0x0002) ACC_PRIVATE // 字段的访问标识符
boolean flag; descriptor: Z flags: (0x0000)
protected char gender; descriptor: C flags: (0x0004) ACC_PROTECTED
public java.lang.String info; descriptor: Ljava/lang/String; flags: (0x0001) ACC_PUBLIC
public static final int COUNTS; descriptor: I flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 1 //常量字段属性: ConstantValue

#########################################方法表集合的信息############################################################################### public com.jvm.test.JavaTest(); //构造器的信息 descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #7 // String java 7: putfield #9 // Field info:Ljava/lang/String; 10: return LineNumberTable: line 16: 0 line 14: 4 line 18: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lcom/jvm/test/JavaTest;
private com.jvm.test.JavaTest(boolean); //构造器的信息 descriptor: (Z)V flags: (0x0002) ACC_PRIVATE Code: stack=2, locals=2, args_size=2 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #7 // String java 7: putfield #9 // Field info:Ljava/lang/String; 10: aload_0 11: iload_1 12: putfield #15 // Field flag:Z 15: return LineNumberTable: line 19: 0 line 14: 4 line 20: 10 line 21: 15 LocalVariableTable: Start Length Slot Name Signature 0 16 0 this Lcom/jvm/test/JavaTest; 0 16 1 flag Z
private void methodPrivate(); descriptor: ()V flags: (0x0002) ACC_PRIVATE Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 24: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Lcom/jvm/test/JavaTest;
int getNum(int); descriptor: (I)I flags: (0x0000) Code: stack=2, locals=2, args_size=2 0: aload_0 1: getfield #19 // Field num:I 4: iload_1 5: iadd 6: ireturn LineNumberTable: line 26: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcom/jvm/test/JavaTest; 0 7 1 i I
protected char showGender(); descriptor: ()C flags: (0x0004) ACC_PROTECTED Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #23 // Field gender:C 4: ireturn LineNumberTable: line 29: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/jvm/test/JavaTest;
public void showInfo(); descriptor: ()V //方法描述符 flags: (0x0001) ACC_PUBLIC //方法的访问标识 Code: //方法的Code属性 stack=3, locals=2, args_size=1 //stack:操作数栈的最大深度 locals:局部变量表所需要的存续空间。args_size:方法接收参数的个数。 //没有参数的时候args_size都是1 如果有参数那么可能是2或者其他的数值 //偏移量 操作码 操作数 0: bipush 10 2: istore_1 字符串的索引 3: getstatic #27 // Field java/lang/System.out:Ljava/io/PrintStream; 6: aload_0 7: getfield #9 // Field info:Ljava/lang/String; 10: iload_1 11: invokedynamic #33, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String; 16: invokevirtual #37 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 19: return // 行号表:指明字节码指令的偏移量与java源程序中代码的行号一一对应关系 LineNumberTable: line 32: 0 line 33: 3 line 34: 19 //局部变量表: 描述局部变量的相关信息。 //这里比如this就是局部变量以及i也是局部变量 有效期可以根据start和length的相加进行得出。 //比如说0 + 20 = 20 所以到0 - 20 也就是 19这里的renturn 所以表示到我们的程序末尾this这个局部变量都有效。 //比如 3 + 17 = 20 从3开始,也就是说到程序末尾 i 这个变量都有效。 LocalVariableTable: Start Length Slot Name Signature 0 20 0 this Lcom/jvm/test/JavaTest; 3 17 1 i I
static {}; //原本是<clinit>() 但是通过ACC_STATIC这个标识 javap给我们反编译成了static descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=1, locals=1, args_size=0 0: ldc #43 // String www.baidu.com 2: astore_0 3: return LineNumberTable: line 11: 0 line 12: 3 LocalVariableTable: Start Length Slot Name Signature}SourceFile: "JavaTest.java" //附加属性 指明当前字节码文件对应的源文件名BootstrapMethods: 0: #65 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; Method arguments: #71 \u0001\u0001InnerClasses: public static final #78= #74 of #76; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

字节码指令集

Java字节码对于虚拟机,就跟汇编对于计算机,属于基本执行指令。

Java虚拟机的指令由一个字节长度的,代表着某种特定操作定义的成为操作码,紧跟其后的就是操作数。

例如: 这里的getfield就表示操作码 #2表示操作数,可以看到好友aload_0只有一个操作码 并没有操作数。

由于限制了Java虚拟机操作码的长度为1个字节(即 0 ~ 255) 这就意味着指令集的操作码总数不可能超过256条。

字节码与数据类型

在Java虚拟机的指令中,大多数的指令都包含了其操作所对应的数据类型信息,例如iload指令用于从局部变量表中加载init类型的数据到操作栈中,而fload指令加载的则是float类型的数据。

操作码指令中都有特殊的字符来表明专门为那种数据类型服务:

1.i 代表 int类型的数据操作

2.l代表long

3.s代表short

4.b代表byte

5.c代表char

6.f代表float

7.d代表double

例如:这里的ireturn表示的就是为int类型的数据操作进行返回。

其他一些类型的操作码没有明确指明操作类型的字母,例如arraylength指令,虽然没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。

局部变量压栈指令

这类指令大体可以分为:

Xload_<n> (x为i,l,f,d,a) n为0~3

x在这里表示他的数据类型,比如i就是int l就是long。

指令表示xload_n表示将第n个局部变量压入到操作数栈中,这个操作数栈就是每一个栈帧用都有存在的一部分。

比如iload_1,fload_0,aload_0等指令,其实aload_n表示将一个对象引用压栈。

指令xload通过指定参数的形式,把局部变量压入栈, 比如aload 4 ,这是因为局部变量的数量可能超过了4个。

例子:

public void load(int num,Object obj,long count,boolean flag,short[] arr){    System.out.println(num);    System.out.println(obj);    System.out.println(count);    System.out.println(flag);    System.out.println(arr);}

首先iload1表示将int类型的数据放到角标为1的局部变量表中,紧接着将obj放到到角标为2的位置,将count变量放到到角标为3的位置,以及将flag放到到角标为5的位置,注意这里count也就是long类型占用了2个字节。那么角标为0的位置放的是一个this,因为我们的方法是非静态的。

我们查看局部变量表可以看到。

紧接着从局部变量表中取出然后压入到操作数栈中。

操作数栈:

常量入栈指令

常量入栈指令的功能是将常数压入到操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列 push系列和ldc指令。

指令const系列:用于对特定的常量入栈,入栈的常量隐含在指令本身里。例如iconst_<i> (i从1到5)

Lconst<l> (l从0到1),fconst<f> (f从0到2) dconst_<d>(d从0到1) acount_null

指令push系列,主要包括bipush和sipush 它们的区别在于接受数据类型的不同,bipush接口8位整数作为参数,sipush接收16位整数 它们都将参数压入栈。

比如:

int a = 5 那么对应的指令就是iconst_5

int j = 6 那么对应的指令就是bipush 6 当大于5的时候就得使用bipush命令了。

指令ldc系列,如果上面指令不能满足 那么可以使用万能的ldc指令,他可以接收9位的参数,改参数指向常量池中的int float 或者 string的索引,将指定的内容压入栈。

类似的还有ldc_w 它接收两个8位的参数,能支持的索引范围大于ldc

如果要压入的元素是long或者double类型的,则使用ldc2_w指令。

比如:

这里的索引为13,也就是索引为13的位置上的数据。

出栈装入局部变量表指令

出栈装入局部变量表指令用于将操作数栈元素弹出后,装入局部变量表的指定位置,用于给局部变量赋值。

这类指令主要以store的形式 比如xstore (x为i l f d a) 也就是代表类型,xstore_n(n 为 0 到3)

指令xstore_n将操作数栈中弹出一个整数,并将他赋值给句变量索引n的位置。

例如:

首先对于非静态的方法 在局部变量表中0角标这个位置就是this。

iload表示将int类型的数据也就是k 从局部变量表中取出然后压入到操作数栈中。

Iconst_2表示将数值2从局部变量表中取出然后压入到操作数栈。

iadd表示k和2这两个值都出栈然后进行相加。

istore表示在iadd相加完成之后将他存储在局部变量表角标为4的位置,因为double占用2个字节,所以没有放在第三个位置。

Ldc2_w#14表示引用常量池中的第14项的数据,也就是12,然后压入栈。

Lstore 5 表示将上面拿到的数据放到局部变量表中角标为5的位置。

ldc #16 表示引用常量池中的第16项的数据 也就是nanchensec,压入栈。

astore表示将nanchensec放到局部变量表中。

ldc #17 引用17项的数据 也就是10.0, 压入栈。

fstore 8 表示10.0放到局部变量表中角标为8的位置。

Ldc2_w #18引用常量池中的第18项 也就是10.0 然后压入栈

dstore_2表示将10.0放到局部变量表中角标为2的位置。

算数指令

算数指令用于对两个操作数栈的值进行某种特定运算,并把结果重新压入操作数栈中。

NaN值使用

当一个操作产生溢出时,将会使用有符号的无穷大表示,如果某个操作结果没有明确的数学定义的话,将会使用NAN值来表示,而且所有使用NAN值作为操作数的算数操作,结果都会返回NAN。

例子:

@Testpublic void method1(){    int i = 10; //一个数除以 0.0 结果就是无穷大    double j = i / 0.0;    System.out.println(j); //无穷大  Infinity
double d1 = 0.0; double d2 = d1 / 0.0; //NAN 不是一个确定的数值 System.out.println(d2);}

算数指令

算数指令解析

首先在局部变量表索引0的位置是this。

使用ldc命令从常量池中将10.0取出来。然后压入到操作数栈。

fstore_1从操作数栈中取出,然后放在局部变量表角标为1的位置。这样之后栈中就没有10.0这个数据了。

fload_1表示从局部变量表中角标为1的位置也就是10.0,取出来然后压入到操作数栈。

fneg表示从操作数栈中,取出10.0然后取负,之后再压入到操作数栈。

fstore_2表示将上一步压入到操作数栈中的-10.0这个值,然后放到局部变量表中。这样的话栈中就没有-10.0这个数据了。

fload2表示将局部变量表中角标为2的数据,也就是-10.0,压入到操作数栈。

fneg表示从操作操作数栈中取出-10.0这个值然后给他取负,取负之后就变成+10.0。然后压入到操作数栈。

fstore_1表示从操作数栈中取出+10.0,然后将数据放到局部变量表中角标为1的位置。这时候栈中就没有+10.0这个数据了。

例子2:

首先局部变量表中角标为0的位置是放的是this。

Bipush 100 表示将100这个数据压入到操作数栈中。

Istore_1表示首先出栈,然后从操作数栈中取出然后将100这个值放到局部变量表的角标为1的位置。

Iload1_1表示将局部变量表中的数据再次压入到栈中。

bipush 10表示压入到操作数栈中。

iadd表示刚压入栈的这两个数据,首先出栈,然后进行相加,相加之后再次压入到操作数栈。

istore_1表示从操作数栈中取出相加之后的数据,然后放到局部变量表中角标为1的位置。

注意:在进行算数指令的话,需要先出栈然后再进行算数。最后再压入栈。

例三:

如下例子结果为10。

public void method6(){    int i = 10;    i = i++;    System.out.println(i);}

分析:

bipush首先将10压入栈,istore_1从栈中取出然后放到局部变量表角标为1的位置。栈中的10就没有了。

Iload_1表示将10再次压入栈,iinc 1 by 1表示将局部变量表中角标为1位置上的10进行加1。

此时局部变量表中角标为1的位置就变成了11。然后istore_1将操作数栈中的10覆盖掉局部变量表中的11。

所以此时局部变量表中角标为1的位置就变成了10。

比较指令

比较指令的作用是比较栈顶两个元素的大小 并将结果入栈。

比较指令有: dcmpg,dcmpl fcmpg fcmpl lcmp

类型转换指令

类型转换指令可以将两种不同的数值类型进行相互转换。也就是除了boolean类型之外的。

宽化类型转换

Java虚拟机直接支持以下数值的宽化类型转换(widening numeric conversion 小范围向大范围指令的安全转换)

int -> long -> float ->double

从int类型到long float以及double对应的指令为 i2l i2f i2d

从long类型到float double类型 对应的指令为: 12f 12d

从float类型到double类型 对应的指令为f2d

例子:

窄类型转换

从int类型转换成byte short 或者char类型 对应的指令有: i2b i2s i2c

从long类型到int类型  l2i

从float类型到int或者long类型 f2i f2l

从double类型到int long或者float类型 d2i d2l d2f

例子:

对象的创建和访问指令
创建类实例的指令

创建类实例的指令为:new

它接收一个操作数,为指向常量池的索引 表示要创建的类型 执行完成之后将对象的引用压入栈。

创建数组的指令

创建数组的指令:newarray anewarray multianewarray。

newarray: 创建基本类型的数组

anewarray: 创建引用类型数组

Multianewarray:创建多维数组

例子1:

public void newInstance(){    Object object = new Object();    File file = new File("aaa.gview");}

首先通过new指令进行创建类实例,他引用到了#23这个索引。#23就是<java/lang/Object> 紧接着压入到栈中。在栈中存储的是对象的地址值。

dup表示复制我们上一步存储在栈中的地址值,复制一份压入栈中。

invokespecial调用object的构造器方法。这时候就需要消耗掉操作数栈中的一个引用地址。

astore_1表示将引用地址值放到局部变量表中角标为1的位置。

紧接着new了一个File的实例,然后进行复制一份地址压入栈。

ldc 将aaa.gview这个字符串压入到操作数栈中。

当调用 invokespecial的时候,aaa.gview和引用地址都会出栈。

astore_2 然后将File的引用地址放到局部变量表角标为2的位置。

例2:

public void newArray(){    int[] intarray = new int[10];    Object[] objectarray = new Object[10];    int[][] array = new int[10][10];    String[][] stringarray = new String[10][10];}

首先将10压入操作数栈。

newarray表示new一个基本数据类型的数组。

然后放到局部变量表。

后面基本上就是从栈中取出来数据之后然后new数组相关的对象。最后存储在局部变量表中。

字段访问指令

对象创建后,就可以通过对象访问指令获取对象实例或数组实例中的字段或者数组元素等信息。

访问类字段(static字段) 或者成为类变量 访问这一类的指令有:getstatic putstatic

访问类实例字段 (非static字段 或者称为实例变量)的指令有: getfield pufield

例1:

首先getstatic #5表示引用常量池中第五项的数据,然后压入到操作数栈中。

ldc #29 表示引用常量池中的数据 拿到hello这个字符串之后然后压入栈。

invokespecial表示调用println这个方法,此时需要将上两步存储的数据出栈。

例2:

new指令表示实例化了一个Order对象,然后将他的地址压入到栈,比如0x1122

dup指令表示将0x1122 复制了一份压入到操作数栈中,也就是说现在操作数栈中有两个关于Order对象的引用地址。

invokespecial指令表示调用了Order的空参构造方法,将栈中的一份引用地址拿出来然后调用空参的构造方法。此时栈中只有一份引用地址。

Astore_1指令表示将栈中的最后一份引用地址取出然后放到局部变量表角标为1的位置。

Aload_1指令表示将局部变量表中的引用地址压入到操作数栈中。

supush 1001指令表示将1001这个数据压入到栈中。

putfield指令表示通过栈中的引用地址找到属性Order.id,给id这个属性赋值为1001。赋值完成之后 1001 和引用地址也就出栈了。

getstatic指令表示将将System.out压入到操作数栈中。

aload_1将局部变量表中的引用地址压入到操作数栈中。

getfield指令表示通过上一步压入的引用地址找到Order.id,找到之后然后将1001这个数据压入到栈中。

invokespecial表示调用println方法,将1001作为参数进行传递进去。那么刚才压入栈的System.out和1001也就出栈了。

ldc将字符串ORDER压入到栈中。

putstatic表示调用了Order.name 因为这个name是静态的,所以不需要类的引用去调用。这时候ORDER这个字符串也就出去了。

getstatic指令表示压入System.out到操作数栈中。

然后将Order.name压入到操作数栈中。

Invokevirtual指令将上两步压入到栈的数据进行调用println方法,那么System.out和ORDER也就出栈了。

数组操作指令

数组操作指令主要有: xastore和xaload指令 具体:

把一个数组元素加载到操作数栈的指令:baload caload saload iaload faload daload aaload

将一个操作数栈的值存储到数组元素中的指令:bastore castore sastore iastore lastore fastore dastore aasore

这里的store不是将操作数栈中的数据存储在局部变量表了,而是将操作数栈中的数据保存到堆空间的数组中。

因为局部变量表中不能存储数组。

如下对应表:

例1:

bipush首先将10这个数据压入到操作数栈中。

Newarray 10 表示创建数组对象 长度为10 并压入到栈中。此时10这个数据就从操作数栈中出栈了。

Astore_1 将数组对象的引用地址放到局部变量表角标为1的位置中。此时这个地址就出栈了

aload_1 将局部变量表中的数据 压入到操作数栈中。

iconst_3 将3这个数据压入到操作数栈中。

bipush 20 将20压入栈,现在操作数栈中有 引用地址,20,3。

iastore这个指令在执行之前需要3个值:值,索引,数组引用地址。那么我们操作数栈中正好有这三个值。

当调用这个指令之后 这三个值就全部出栈了,出栈之后通过数组引用地址找到堆空间的数组,通过3找到了索引位置。

然后将20这个值赋值到数组的3索引上。

getstatic 将System.out压入栈。

aload_1 将局部变量角标为1的数据压入到栈。

inconst_1 将1这个数据压入到栈中。

在执行iaload之前,1 和 引用地址就出栈了。出栈之后通过引用地址找到堆空间的数组,然后再值压入到栈中。

invokevirtual 调用println方法。

类型检查指令

检查类实例或者数组类型的指令:instanceof,checkcast

Instanceof用来判断给定对象是否是某一个类的实例,它会将判断结果压入操作数栈中。

Checkcast用于检查类型强制转换是否可以进行,如果可以进行,那么checkcast指令不会改变操作数栈,否则抛出异常:ClassCastException异常。

例:

首先aload1将我们的obj加入到栈中,这个obj是一个形参列表。

instanceof #17命令表示判断obj是否是String类型的实例。

Ifeq 判断满足的话就继续往下走,如果不满足的话就直接跳到12的位置,12的位置就是aconst_null。

aload_1再次将局部变量表中的obj压入到操作数栈中。

checkcast 进行强制类型转换。

方法调用指令

方法调用指令:

Invokevirtual:调用对象的实例方法,根据对象的类型进行分派,支持多态,最常见的方法分派方式。

Invokeinterface指令用于调用接口方法,他会运行时搜索由特定对象所实现的接口方法,并找出合适的方法调用。

Invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法,私有方法,和父类方法,这些方法都是静态类型绑定的。不会再调用时进行动态派发。

Invokestatic指令用于调用命名类中的方法,这是静态绑定的。

Invokedynamic 调用动态方法。

例1:

方法返回指令

方法调用结束前,需要进行返回,方法返回指令时根据返回值类型区分的。

操作数栈管理指令

这类指令包括如下:

将一个或者多个元素从栈顶弹出 并且直接废弃 pop pop2

复制栈顶一个或多个数值并将复制或者双份重新压入栈。dup dup2 dup_x1 dup2_x1 dup_x2 dup2_x2

控制转移指令
比较指令

比较指令有:dcmpg,dcmpl,fcmpg,fcmpl,lcmp。

例如:

指令fcmpg和fcmpl都是从栈中弹出两个操作数,并将他们做比较,设栈顶为v2 栈顶顺位的第二位为v1。如果v1 == v2 则压入栈为0,

如果v1>v2 则压入栈为1,如果v1<v2则压入栈为-1。

两个指令的不同之处在于,如果遇到NaN值,fcmpg会压入1,而fcmpl会压入-1。

条件跳转指令

条件跳转指令通常和比较指令结合使用。在条件跳转指令执行之前,一看可以先用比较指令进行栈顶元素的比较,然后进行条件跳转。

条件跳转指令有: ifeq,iflt,ifle,ifne,ifgt,ifge,ifnull,ifnonnull。这些指令都接收两个字节的操作数,用于计算跳转的位置。

例1:

Iconst_0将数值0压入到操作数栈中。

Istore_1将栈中的0取出,然后放到局部变量表角标为1的位置。

Iload_1将局部变量表中的0这个值压入到操作数栈中。

ifne指令表示判断操作数栈中的值 是否不等于0。如果满足这个条件的话,那么就发生跳转,跳转到第12行。也就是bipush 20这一行。

bipush 10 将10这个值压入到操作数栈中。

istore_1 将操作数栈中的10保存到局部变量表角标为1的位置,此时局部变量表角标为1的位置 0这个值就变成了10。

goto 15指令表示无条件跳转 直接就跳转到15这一行,页面就是return。

例2:

首先ldc引用常量池中的第2项数据也就是9.0压入到栈中。

Fstore_1首先出栈然后将操作数栈中的9.0放到局部变量表中1的位置。

ldc #3 将10.0压入到栈中,fstore_2首先出栈然后将10.0放到局部变量表中2的位置。

getstatic 将System.out压入到操作数栈中。

将fload_1和fload_2的值压入到操作数栈中。

fcmpg把上面压入的这两个值弹出然后进行比较 比较之后的是值是-1,然后将-1压入到操作数栈中。

ifge指令表示判断 -1 是否大于0或者等于0 ,显然是不成立的,所以不需要跳转。

iconst_1将1压入到操作数栈中。

goto 20 无条件跳转到20,然后调用println方法。

比较跳转指令

比较指令类似于比较指令和条件跳转指令的结合体,它将比较和跳转两个步骤和一。

这类指令有 if_icmpeq if_icmpne if_icmplt if_icmpgt if_icmple if_icmpge if_acmpeq和if_acmpne

这里的i表示的就是int整数类型也包括short和byte等类型。

多条件分支跳转指令

多条件分支跳转指令是专门为switch-case语句设计的,主要有tableswitch和lookupswitch。

例1:

Iload_1将局部变量表中的值压入到栈中。

tableswitch 1to 3 表示1到3,比如传过来的值为1,那么就直接到case 1这里,直接跳转到28。

例2:

lookupswitch 3指令表示一个一个进行比较。

无条件跳转指令

目前主要使用的无条件跳转指令为goto,指令goto接收两个字节的操作数,共同组成一个带符号的整数,用于指定指令的偏移量。

指令用于跳转到偏移量给定的位置处。

如果偏移量太大,那么可以使用goto_w和goto有相同的作用。

jsr 和 jsr_w,ret虽然也是条件跳转,但是已经被废弃了。

关注如下公众号一块学习哦


文章来源: http://mp.weixin.qq.com/s?__biz=MzAwMDQwNTE5MA==&mid=2650247264&idx=1&sn=582db25077b000aa0f708ef8a5e44bb0&chksm=836a4a941f4c69354b4f0c9fcb871b6c99dee644514a5f76c9ba117c42516d0625c1bb2cb8af&scene=0&xtrack=1#rd
如有侵权请联系:admin#unsafe.sh