一、字节码的执行模型
VM是一台基于栈的计算机器。每个线程都有一个独属于自己的线程栈(JVM stack),
用于存储 栈帧 (Frame)。每一次方法调用,JVM都会自动创建一个栈帧。 栈帧 由操作数栈 , 局部变量数组 以及一个 class引用 组成。 class引用 指向当前方法在运行时常量池中对应的class)。
二、方法调用的常见指令
- invokestatic,顾名思义,这个指令用于调用某个类的静态方法,这是方法调用指令中最
快的一个。
- invokespecial, 用来调用构造函数,但也可以用于调用同一个类中的 private 方法, 以及
可见的超类方法
- invokevirtual,如果是具体类型的目标对象,invokevirtual 用于调用公共,受保护和
package 级的私有方法
- invokeinterface,当通过接口引用来调用方法时,将会编译为 invokeinterface 指令
- invokedynamic,JDK7 新增加的指令,是实现“动态类型语言”(Dynamically Typed
Language)支持而进行的升级改进,同时也是 JDK8 以后支持 lambda 表达式的实现基
础。
三、举个栗子
前面介绍了字节码文件的结果以及常见指令。这里举一个例子,完整的回顾一下上面的知识。
public class ShowJavaByteCode {
private Integer age;
public static final int fa = 10;
public final int fa2 = 30;
private static int sa = 20;
static {
sa = 30;
}
public void pirnt(){
age=10;
System.out.println("-----------------this is my com.dark.String--------------");
}
public static void main(java.lang.String[] args){
ShowJavaByteCode showJavaByteCode=new ShowJavaByteCode();
showJavaByteCode.pirnt();
}
}
这个栗子中包含了以下信息:
- 属性
- 静态常量属性
- 常量属性
- 静态属性
- 静态块
- 一般方法
- 静态方法
字节码内容如下:
Classfile /F:/IdeaProjects/testString/target/classes/com/dark/ShowJavaByteCode.class
Last modified 2021-3-5; size 1055 bytes
MD5 checksum ad6dd0d4a42f5da54b4f3ad982b6a775
Compiled from "ShowJavaByteCode.java"
public class com.dark.ShowJavaByteCode
minor version: 0
major version: 49
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #12.#38 // java/lang/Object."<init>":()V
#2 = Fieldref #8.#39 // com/dark/ShowJavaByteCode.fa2:I
#3 = Methodref #40.#41 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#4 = Fieldref #8.#42 // com/dark/ShowJavaByteCode.age:Ljava/lang/Integer;
#5 = Fieldref #43.#44 // java/lang/System.out:Ljava/io/PrintStream;
#6 = String #45 // -----------------this is my com.dark.String--------------
#7 = Methodref #46.#47 // java/io/PrintStream.println:(Ljava/lang/String;)V
#8 = Class #48 // com/dark/ShowJavaByteCode
#9 = Methodref #8.#38 // com/dark/ShowJavaByteCode."<init>":()V
#10 = Methodref #8.#49 // com/dark/ShowJavaByteCode.pirnt:()V
#11 = Fieldref #8.#50 // com/dark/ShowJavaByteCode.sa:I
#12 = Class #51 // java/lang/Object
#13 = Utf8 age
#14 = Utf8 Ljava/lang/Integer;
#15 = Utf8 fa
#16 = Utf8 I
#17 = Utf8 ConstantValue
#18 = Integer 10
#19 = Utf8 fa2
#20 = Integer 30
#21 = Utf8 sa
#22 = Utf8 <init>
#23 = Utf8 ()V
#24 = Utf8 Code
#25 = Utf8 LineNumberTable
#26 = Utf8 LocalVariableTable
#27 = Utf8 this
#28 = Utf8 Lcom/dark/ShowJavaByteCode;
#29 = Utf8 pirnt
#30 = Utf8 main
#31 = Utf8 ([Ljava/lang/String;)V
#32 = Utf8 args
#33 = Utf8 [Ljava/lang/String;
#34 = Utf8 showJavaByteCode
#35 = Utf8 <clinit>
#36 = Utf8 SourceFile
#37 = Utf8 ShowJavaByteCode.java
#38 = NameAndType #22:#23 // "<init>":()V
#39 = NameAndType #19:#16 // fa2:I
#40 = Class #52 // java/lang/Integer
#41 = NameAndType #53:#54 // valueOf:(I)Ljava/lang/Integer;
#42 = NameAndType #13:#14 // age:Ljava/lang/Integer;
#43 = Class #55 // java/lang/System
#44 = NameAndType #56:#57 // out:Ljava/io/PrintStream;
#45 = Utf8 -----------------this is my com.dark.String--------------
#46 = Class #58 // java/io/PrintStream
#47 = NameAndType #59:#60 // println:(Ljava/lang/String;)V
#48 = Utf8 com/dark/ShowJavaByteCode
#49 = NameAndType #29:#23 // pirnt:()V
#50 = NameAndType #21:#16 // sa:I
#51 = Utf8 java/lang/Object
#52 = Utf8 java/lang/Integer
#53 = Utf8 valueOf
#54 = Utf8 (I)Ljava/lang/Integer;
#55 = Utf8 java/lang/System
#56 = Utf8 out
#57 = Utf8 Ljava/io/PrintStream;
#58 = Utf8 java/io/PrintStream
#59 = Utf8 println
#60 = Utf8 (Ljava/lang/String;)V
{
public static final int fa;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 10
public final int fa2;
descriptor: I
flags: ACC_PUBLIC, ACC_FINAL
ConstantValue: int 30
public com.dark.ShowJavaByteCode();
descriptor: ()V
flags: 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: bipush 30
7: putfield #2 // Field fa2:I
10: return
LineNumberTable:
line 6: 0
line 10: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/dark/ShowJavaByteCode;
public void pirnt();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: bipush 10
3: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: putfield #4 // Field age:Ljava/lang/Integer;
9: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
12: ldc #6 // String -----------------this is my com.dark.String--------------
14: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: return
LineNumberTable:
line 17: 0
line 18: 9
line 19: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lcom/dark/ShowJavaByteCode;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #8 // class com/dark/ShowJavaByteCode
3: dup
4: invokespecial #9 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #10 // Method pirnt:()V
12: return
LineNumberTable:
line 22: 0
line 23: 8
line 24: 12
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 args [Ljava/lang/String;
8 5 1 showJavaByteCode Lcom/dark/ShowJavaByteCode;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 20
2: putstatic #11 // Field sa:I
5: bipush 30
7: putstatic #11 // Field sa:I
10: return
LineNumberTable:
line 11: 0
line 13: 5
line 14: 10
}
SourceFile: "ShowJavaByteCode.java"
结合前面介绍的字节码结构来看,加深对字节码的理解。
四、总结:
今天介绍了字节码常用指令,并通过一个栗子来把字节码的内容穿起来。加深印象。在字节码中可以证实其中存在默认构造函数,所以默认构造函数是Java编译器生成的, 而不是运行时JVM自动生成的。