基础知识
执行模式
- 解释执行:是使用解释器会将我们的一句句代码解释成机器可以识别的二进制代码来执行,如Dalvik
- 编译执行:源程序先通过编译器(负责将源程序翻译成目标机器指令)翻译成机器指令,通过编译-->链接-->目标可执行文件,然后执行;即提前将所有源代码一次性转换成二进制指令,也就是生成一个可执行程序,如旧JIT,C++
- 两者兼备:新JIT 无论什么执行模式,统一的一点是字节码是基于栈的执行引擎,后面的例子可以说明这一点。下面就看看其数据结构
执行数据结构
栈帧:栈中的元素
- 局部变量表:用于存储局部变量
- 操作数栈:用于执行函数以及入参
- 动态链接:支持运行时的方法引用转化
- 返回地址:支持方法返回
基本指令
astore_n
: 栈顶引用型数值存入第n个本地变量invokespecial
:invokevirtual
: 普通方法调用dup
: 复制栈顶数值并将复制值压入栈顶ldc
: 推送常量到栈顶aload_n
:将第n个引用类型的变量推送到栈顶
实例分析
Java源码
package jvm;
public class Language {
public int count = 1;
public String getContent() {
return "hello world";
}
public String getContent2() {
return "hello world 2";
}
}
public class Person {
private final String name = "person";
private final Language mLanguage = new Language();
public void say() {
System.out.println("Person say " + mLanguage.getContent());
}
public static void main(String[] args) {
Person person = new Person();
person.say();
}
}
javap -v Person 输出
public class jvm.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #18.#33 // java/lang/Object."<init>":()V
#2 = String #34 // person
#3 = Fieldref #15.#35 // jvm/Person.name:Ljava/lang/String;
#4 = Class #36 // jvm/Language
#5 = Methodref #4.#33 // jvm/Language."<init>":()V
#6 = Fieldref #15.#37 // jvm/Person.mLanguage:Ljvm/Language;
#7 = Fieldref #38.#39 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #40 // java/lang/StringBuilder
#9 = Methodref #8.#33 // java/lang/StringBuilder."<init>":()V
#10 = String #41 // Person say
#11 = Methodref #8.#42 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#12 = Methodref #4.#43 // jvm/Language.getContent:()Ljava/lang/String;
#13 = Methodref #8.#44 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#14 = Methodref #45.#46 // java/io/PrintStream.println:(Ljava/lang/String;)V
#15 = Class #47 // jvm/Person
#16 = Methodref #15.#33 // jvm/Person."<init>":()V
#17 = Methodref #15.#48 // jvm/Person.say:()V
#18 = Class #49 // java/lang/Object
#19 = Utf8 name
#20 = Utf8 Ljava/lang/String;
#21 = Utf8 ConstantValue
#22 = Utf8 mLanguage
#23 = Utf8 Ljvm/Language;
#24 = Utf8 <init>
#25 = Utf8 ()V
#26 = Utf8 Code
#27 = Utf8 LineNumberTable
#28 = Utf8 say
#29 = Utf8 main
#30 = Utf8 ([Ljava/lang/String;)V
#31 = Utf8 SourceFile
#32 = Utf8 Person.java
#33 = NameAndType #24:#25 // "<init>":()V
#34 = Utf8 person
#35 = NameAndType #19:#20 // name:Ljava/lang/String;
#36 = Utf8 jvm/Language
#37 = NameAndType #22:#23 // mLanguage:Ljvm/Language;
#38 = Class #50 // java/lang/System
#39 = NameAndType #51:#52 // out:Ljava/io/PrintStream;
#40 = Utf8 java/lang/StringBuilder
#41 = Utf8 Person say
#42 = NameAndType #53:#54 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#43 = NameAndType #55:#56 // getContent:()Ljava/lang/String;
#44 = NameAndType #57:#56 // toString:()Ljava/lang/String;
#45 = Class #58 // java/io/PrintStream
#46 = NameAndType #59:#60 // println:(Ljava/lang/String;)V
#47 = Utf8 jvm/Person
#48 = NameAndType #28:#25 // say:()V
#49 = Utf8 java/lang/Object
#50 = Utf8 java/lang/System
#51 = Utf8 out
#52 = Utf8 Ljava/io/PrintStream;
#53 = Utf8 append
#54 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#55 = Utf8 getContent
#56 = Utf8 ()Ljava/lang/String;
#57 = Utf8 toString
#58 = Utf8 java/io/PrintStream
#59 = Utf8 println
#60 = Utf8 (Ljava/lang/String;)V
{
private final java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_FINAL
ConstantValue: String person
private final jvm.Language mLanguage;
descriptor: Ljvm/Language;
flags: ACC_PRIVATE, ACC_FINAL
public jvm.Person();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String person
7: putfield #3 // Field name:Ljava/lang/String;
10: aload_0
11: new #4 // class jvm/Language
14: dup
15: invokespecial #5 // Method jvm/Language."<init>":()V
18: putfield #6 // Field mLanguage:Ljvm/Language;
21: return
LineNumberTable:
line 3: 0
line 5: 4
line 6: 10
public void say();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
// 拿到一个静态对象
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
// new三兄弟又出现了
3: new #8 // class java/lang/StringBuilder
6: dup
7: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
// push常量池中的常量#10('Person Say')到栈顶
10: ldc #10 // String Person say
// 调用方法StringBuilder.append()
12: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
// push本地变量表的第0个即this到栈顶,为了调用getfield
15: aload_0
// 从字段集合中拿到#6(mLanguage)到栈顶
16: getfield #6 // Field mLanguage:Ljvm/Language;
// 调用getContent方法,#12调用链路为(#4.#55):#56 -> mLanguage.getContent(): String
19: invokevirtual #12 // Method jvm/Language.getContent:()Ljava/lang/String;
// 同上注释,最终解析为 builder.append(): StringBuilder。其实默认的英文注释已经写得很明白了
22: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
28: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: return
LineNumberTable:
line 9: 0
line 10: 31
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
// new, dup, invokespecial是一个经典组合,其说明了new在本质上不是一个原子操作
// 创建一个对象引用(可以通过常量池表查看#15对应的对象),并入栈
0: new #15 // class jvm/Person
// 复制该对象引用并入栈
3: dup
// 调用Person的init方法(此处也可以通过常量池表查看)
4: invokespecial #16 // Method "<init>":()V
// 存入栈顶(此时为Person的引用)到第一个本地变量表,这里其实是赋值给本地变量person
7: astore_1
// 加载本地变量表索引为1的值入栈顶(索引为0的是this),这里是准备进行方法调用了
8: aload_1
// 调用#17所指定的方法,这里的方法是say()
9: invokevirtual #17 // Method say:()V
12: return
LineNumberTable:
line 13: 0
line 14: 8
line 15: 12
}
SourceFile: "Person.java"