手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。
平台 | 地址 |
---|---|
CSDN | https://blog.csdn.net/sinat_28690417 |
简书 | https://www.jianshu.com/u/3032cc862300 |
个人博客 | https://yiyuery.github.io/NoteBooks/ |
Target
JVM 系列学习知识
:
文本主要就JVM结构和字节码文件,进行分析来展开JVM的学习,后续系列文章会从JVM的多个方面的进行知识总结。
JVM: Java Vitual Machine
解决Java程序在一处开发,多处运行的的低层依赖问题。
JVM 结构
分层
- 类加载子系统
- 运行时数据区
- 执行引擎
类加载子系统
Java 类加载器 子系统在运行时第一次引用类时加载,链接和初始化类文件。它负责从文件系统,网络或任何其他来源加载类文件。Java、Bootstrap、Extension和Application(System)类加载器中使用了三个默认的类加载器。
运行时数据区域
程序计数器
作用:字节码解释器工作时根据改变计数器的值来选取下一条需要执行的字节码指令。
范围:线程私有
Java 虚拟机栈
作用:描述的是Java方法执行的内存模型:每个方法在运行时都会创建一个栈帧,用于存储方法运行时需要的数据。
范围:线程私有
本地方法栈
作用:本地方法栈为虚拟机使用到的Native方法服务
Java 堆
作用:所有数据对象实例以及数组基本都在堆上进行分配
范围:线程共享
方法区
作用:用于存储已被虚拟机加载的类信息、常量、静态变量即时编译后的代码等数据。
范围:线程共享
- 运行时常量池:存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放
直接内存
作用:NIO数据缓存,用于Java堆中直接操作Native堆的数据。
字节码文件
定义一个简单类
public class App {
public int calculate() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
App app = new App();
App app2 = new App();
System.out.println(app.calculate());
System.out.println(app2.calculate());
}
}
输出字节码文件
编译: javac App.java
字节码文件:javap -c App.class > App.txt
App.txt
Compiled from "App.java"
public class src.main.java.com.example.jvm.App {
public src.main.java.com.example.jvm.App();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int calculate();
Code:
//以下为字节码指令,前面的数字为程序计数器待记录的值,和程序运行到哪一行有关
0: iconst_1 //将一个常量加载到操作数栈 1
1: istore_1 //将一个数值从操作数栈存储到局部变量表 a=1
2: iconst_2 //将一个常量加载到操作数栈 2
3: istore_2 //将一个数值从操作数栈存储到局部变量表 b=2
4: iload_1 //将一个局部变量加载到操作栈a
5: iload_2 //将一个局部变量加载到操作栈b
6: iadd //将栈顶两int型数值相加并将结果压入栈顶
7: bipush 10 //将常量10加载到操作数栈
9: imul //将栈顶两int型数值相乘并将结果压入栈顶
10: istore_3 //将一个数值从操作数栈存储到局部变量表 c=(1+2)*10=30
11: iload_3 //将一个局部变量加载到操作栈c
12: ireturn //ireturn(当返回值是boolean、byte、char、short和int类型时使用)方法返回指令
public static void main(java.lang.String[]);
Code:
0: new #2 // class src/main/java/com/example/jvm/App
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: new #2 // class src/main/java/com/example/jvm/App
11: dup
12: invokespecial #3 // Method "<init>":()V
15: astore_2
16: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #5 // Method calculate:()I
23: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
26: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_2
30: invokevirtual #5 // Method calculate:()I
33: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
36: return
}
方法栈帧字节码分析
public int calculate();
Code:
//以下为字节码指令,前面的数字为程序计数器待记录的值,和程序运行到哪一行有关
0: iconst_1 //将一个常量加载到操作数栈 1
1: istore_1 //将一个数值从操作数栈存储到局部变量表 a=1
2: iconst_2 //将一个常量加载到操作数栈 2
3: istore_2 //将一个数值从操作数栈存储到局部变量表 b=2
4: iload_1 //将一个局部变量加载到操作栈a
5: iload_2 //将一个局部变量加载到操作栈b
6: iadd //将栈顶两int型数值相加并将结果压入栈顶
7: bipush 10 //将常量10加载到操作数栈
9: imul //将栈顶两int型数值相乘并将结果压入栈顶
10: istore_3 //将一个数值从操作数栈存储到局部变量表 c=(1+2)*10=30
11: iload_3 //将一个局部变量加载到操作栈c
12: ireturn //ireturn(当返回值是boolean、byte、char、short和int类型时使用)方法返回指令
详细分析
输出堆栈大小、各方法的 locals 及 args 数,以及class文件的编译版本
javap -verbose App.class > App3.txt
Classfile /Users/xiazhaoyang/Capsule/repository/gitee/architectrue-adventure/code-examples/code-jvm-analysis/src/src/main/java/com/example/jvm/App.class
Last modified Sep 5, 2019; size 543 bytes
MD5 checksum e51368eac3771cd969c9f3d3f91bcaed
Compiled from "App.java"
public class src.main.java.com.example.jvm.App
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#18 // java/lang/Object."<init>":()V
#2 = Class #19 // src/main/java/com/example/jvm/App
#3 = Methodref #2.#18 // src/main/java/com/example/jvm/App."<init>":()V
#4 = Fieldref #20.#21 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #2.#22 // src/main/java/com/example/jvm/App.calculate:()I
#6 = Methodref #23.#24 // java/io/PrintStream.println:(I)V
#7 = Class #25 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 calculate
#13 = Utf8 ()I
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 SourceFile
#17 = Utf8 App.java
#18 = NameAndType #8:#9 // "<init>":()V
#19 = Utf8 src/main/java/com/example/jvm/App
#20 = Class #26 // java/lang/System
#21 = NameAndType #27:#28 // out:Ljava/io/PrintStream;
#22 = NameAndType #12:#13 // calculate:()I
#23 = Class #29 // java/io/PrintStream
#24 = NameAndType #30:#31 // println:(I)V
#25 = Utf8 java/lang/Object
#26 = Utf8 java/lang/System
#27 = Utf8 out
#28 = Utf8 Ljava/io/PrintStream;
#29 = Utf8 java/io/PrintStream
#30 = Utf8 println
#31 = Utf8 (I)V
{
public src.main.java.com.example.jvm.App();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 24: 0
public int calculate();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
LineNumberTable:
line 27: 0
line 28: 2
line 29: 4
line 30: 11
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
//#2指向Constant pool的#2->#19类文件的引用
0: new #2 // class src/main/java/com/example/jvm/App
//复制栈顶数值(数值不能是long或double类型的)并将复制值压入栈顶
3: dup
//指令用于调用一些需要特殊处理的实例方法,包括实例初始化(<init>)方法、私有方法和父类方法。
4: invokespecial #3 // Method "<init>":()V
//将栈顶引用型数值存入第二个本地变量
7: astore_1
8: new #2 // class src/main/java/com/example/jvm/App
11: dup
12: invokespecial #3 // Method "<init>":()V
//将栈顶引用型数值存入第三个本地变量
15: astore_2
//访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令 #20,#21 (类,静态方法)
16: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
//将第二个引用类型本地变量推送至栈顶
19: aload_1
//指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式
20: invokevirtual #5 // Method calculate:()I
23: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
26: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_2
30: invokevirtual #5 // Method calculate:()I
33: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
36: return
LineNumberTable:
line 34: 0
line 35: 8
line 36: 16
line 37: 26
line 39: 36
}
SourceFile: "App.java"
流程解析
public int calculate() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
前文提到的,每个方法对应的再JVM中的存储结构就是一个栈帧,每个栈帧里有自己的程序计数器,上面的这个简单的calculate
在运行时的流程大致如下:
- 1放入操作数栈,
- 1出栈,存到局部变量a中
- 局部变量a放到操作数栈
- 2入操作数栈
- 2出栈,存到局部变量b中
- b放到操作数栈中
- a,b两个操作数进行相加操作,再将结果3压入栈
- 10 入操作数栈
- 3和10进行相乘,并将结果30再次入操作数栈
- 30 出栈,存到局部变量c中
- 方法出口指向下一个栈帧起始位置
REFERENCES
JVM 字节码指令
JDK 命令行
JVM
更多
扫码关注
架构探险之道
,回复文章标题,获取本文相关源码和资源链接
知识星球(扫码加入获取历史源码和文章资源链接)