java虚拟机(十四)--字节码指令
字节码指令其实是很重要的,在之前学习String等内容,深入到字节码层面很容易找到答案,而不是只是在网上寻找答案,还有可能是错误的。
PS:本文基于jdk1.8
首先写个简单的类:
public class Test {
public static Integer f1() {
int a = 1;
int b = 2;
return a + b;
}
public static void main(String[] args) {
int m = 100;
int c = f1();
System.out.println(m + c);
}
}
反编译:
先通过javac编译,然后通过javap -verbose Test.class > Test.txt把反编译结果重定向到txt文件中
//类文件
Classfile /D:/Java/project/monitor/target/classes/com/it/test1/Test.class
//最后修改,文件大小
Last modified 2019-7-16; size 785 bytes
MD5 checksum 1dc6eb4c2e233f63edbb50e709c20111
//编译来自Test.java
Compiled from "Test.java"
//以下为类信息
public class com.it.test1.Test
//jdk版本
minor version: 0
major version: 52
//类的访问修饰符,public和super
flags: ACC_PUBLIC, ACC_SUPER
//2、常量池,下面1,2,3,4....50,相当于索引,这部分简单理解就行了,主要是程序部分
Constant pool:
//Methodref方法引用,#8.#29代表引用第8行和29行
#1 = Methodref #8.#29 // java/lang/Object."<init>":()V
//自动装箱,执行Integer.valueOf()
#2 = Methodref #30.#31 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#3 = Methodref #7.#32 // com/it/test1/Test.f1:()Ljava/lang/Integer;
#4 = Methodref #30.#33 // java/lang/Integer.intValue:()I
//Fieldref字段引用,L为引用类型,格式为L ClassName;注意最后还有冒号;
#5 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #36.#37 // java/io/PrintStream.println:(I)V
//类
#7 = Class #38 // com/it/test1/Test
#8 = Class #39 // java/lang/Object
#Utf8可以理解为字符串,<init>相当于构造函数
#9 = Utf8 <init>
//()V,无参,返回值为void
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable //本地变量表
#14 = Utf8 this
#15 = Utf8 Lcom/it/test1/Test;
#16 = Utf8 f1
#17 = Utf8 ()Ljava/lang/Integer;
#18 = Utf8 a
#19 = Utf8 I
#20 = Utf8 b
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 m
#26 = Utf8 c
#27 = Utf8 SourceFile
#28 = Utf8 Test.java
//NameAndType,名称和返回值
#29 = NameAndType #9:#10 // "<init>":()V
#30 = Class #40 // java/lang/Integer
#31 = NameAndType #41:#42 // valueOf:(I)Ljava/lang/Integer;
#32 = NameAndType #16:#17 // f1:()Ljava/lang/Integer;
#33 = NameAndType #43:#44 // intValue:()I
#34 = Class #45 // java/lang/System
#35 = NameAndType #46:#47 // out:Ljava/io/PrintStream;
#36 = Class #48 // java/io/PrintStream
#37 = NameAndType #49:#50 // println:(I)V
#38 = Utf8 com/it/test1/Test
#39 = Utf8 java/lang/Object
#40 = Utf8 java/lang/Integer
#41 = Utf8 valueOf
#42 = Utf8 (I)Ljava/lang/Integer;
#43 = Utf8 intValue
#44 = Utf8 ()I
#45 = Utf8 java/lang/System
#46 = Utf8 out
#47 = Utf8 Ljava/io/PrintStream;
#48 = Utf8 java/io/PrintStream
#49 = Utf8 println
#50 = Utf8 (I)V
//程序部分开始
{
public com.it.test1.Test();
//默认构造器,无参,无返回值
descriptor: ()V
//修饰符public
flags: ACC_PUBLIC
//Code部分
Code:
# 操作数栈的深度2
# 本地变量表最大长度(slot为单位),64位的是2个,其他是1个,索引从0开始,如果是非static方法索引0代表this,后面是入参,后面是本地变量
# 1个参数,实例方法多一个this参数
//args_size
stack=1, locals=1, args_size=1
//aload_<n>从本地变量加载引用,n为当前栈帧中局部变量数组的索引
0: aload_0
//invokespecial调用实例方法; 对超类,私有和实例初始化方法调用的特殊处理
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
//行号的表,line后面的数字代表代码的行号,代表上面字节码中的0,就是aload_0
LineNumberTable:
line 3: 0
//本地变量表,非static方法,0位this,static方法,就是第一个变量
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/it/test1/Test; public static java.lang.Integer f1();
descriptor: ()Ljava/lang/Integer;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0
//将常量1push到操作数栈中
0: iconst_1
//将操作数栈中栈顶元素存储到本地变量表的索引0中
//这两步对应着int a = 1;
1: istore_0
2: iconst_2
//这两步对应着int b = 2;
3: istore_1
//将int类型的本地变量0的数据压入操作数栈
4: iload_0
5: iload_1
//int类型相加
6: iadd
//调用了第二行,是一个方法引用,执行完毕,清空操作数栈,此时本地变量表数据还在
7: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
//返回引用,会把本地变量表清空
10: areturn
LineNumberTable:
line 6: 0
line 7: 2
line 8: 4
LocalVariableTable:
Start Length Slot Name Signature
2 9 0 a I
4 7 1 b I public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: bipush 100
2: istore_1
//invokestatic执行静态方法,invokevirtual执行实例方法
3: invokestatic #3 // Method f1:()Ljava/lang/Integer;
6: invokevirtual #4 // Method java/lang/Integer.intValue:()I
9: istore_2
10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_1
14: iload_2
15: iadd
16: invokevirtual #6 // Method java/io/PrintStream.println:(I)V
19: return
LineNumberTable:
line 12: 0
line 13: 3
line 14: 10
line 15: 19
LocalVariableTable://数组类型参数,作为本地变量表索引0位置的数据
Start Length Slot Name Signature
0 20 0 args [Ljava/lang/String;
3 17 1 m I
10 10 2 c I
}
SourceFile: "Test.java"
上面对基本的字节码都有解释了,这里以f1()为例,通过图例更加详细的解释


字节码相关内容,可以参考官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/
1、flags表示类访问和属性修饰符

字段描述符:

方法描述符:

i++和++i
代码:
public static void f1() {
int i = 0;
int j = i++;
}
public static void f2() {
int i = 0;
int j = ++i;
}
public static void main(String[] args) {
f1();
f2();
}
反编译:
f1():
0: iconst_0 //常量0push到操作数栈的栈顶
1: istore_0 //将操作数栈的栈顶数据存储到本地变量表的索引0的位置
2: iload_0
3: iinc 0, 1 //将本地变量表的索引0的数据+1
6: istore_1 //将操作数栈的栈顶数据存储到本地变量表的索引1的位置
7: return f2():
0: iconst_0
1: istore_0
2: iinc 0, 1 //将本地变量表的索引0的数据+1
5: iload_0 //此时索引0的数据为1,load到操作数栈
6: istore_1 ////将操作数栈的栈顶数据存储到本地变量表的索引1的位置
7: return
从上面我们很容易看到二者的区别
PS:for循环中i++和++i没有效率差别,字节码完全一样的
try-Cache字节码:
代码:
public static String f1() {
String str = "hello1";
try{
return str;
}
finally{
str = "imooc";
}
}
public static void main(String[] args) {
f1();
}
反编译:
0: ldc #2 //从运行时常量池中加载字符串hello,然后push到操作数栈
2: astore_0 //将操作数栈的栈顶数据存储到本地变量表的索引0的位置
3: aload_0 //
4: astore_1 //字符串hello,存在本地变量表的两个位置
5: ldc #3 // String imooc
7: astore_0 //将字符串imooc存储到本地变量表的索引0的位置,用imooc覆盖了hello
8: aload_1 //load本地变量表中索引1位置的数据
9: areturn //所以返回的是hello,而不是imooc //假如发生异常,就会走下面的代码
10: astore_2 //将异常存储到本地变量表的索引2的位置
11: ldc #3 // String imooc
13: astore_0
14: aload_2 //将索引2的位置的异常信息load出去
15: athrow //跑出异常
我们从字节码中看到无论是否发生异常,都会执行finally的内容,但是我们的return并不是finally的内容
我们再测试一下:
public static String f1() {
String str = "hello1";
try{
return str;
}
finally{
str = "imooc";
return "111";
}
}
public static void main(String[] args) {
System.out.println(f1());
}
反编译:
0: ldc #2 // String hello1
2: astore_0
3: aload_0
4: astore_1
5: ldc #3 // String imooc
7: astore_0
8: ldc #4 // String 111 将字符串111push到操作数栈的栈顶
10: areturn 11: astore_2
12: ldc #3 // String imooc
14: astore_0
15: ldc #4 // String 111 将字符串111push到操作数栈的栈顶
17: areturn
所以无论是否发生异常,返回的都是字符串111,我们一般情况下不要在finally中使用return,很容易出现错误
String Constant Variable:
我们知道String的字符串拼接就是会new一个StringBuilder,然后append这个字符串,然后调用toString(),在for循环中效率很低。但是如果是final修饰的常量就不一定了。
代码:
public static void f1() {
final String x="hello";
final String y=x+"world";
String z=x+y;
System.out.println(z);
}
public void f2(){
final String x="hello";
String y=x+"world";
String z=x+y;
System.out.println(z);
}
反编译:
f1():
0: ldc #2 // String hello //从常量池把hello字符串push到操作数栈
2: astore_0
3: ldc #3 // String helloworld //从常量池把helloworld字符串push到操作数栈
5: astore_1
6: ldc #4 // String hellohelloworld //从常量池把hellohelloworld字符串push到操作数栈
8: astore_2
9: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
12: aload_2
13: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return f2():
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String helloworld
5: astore_2
6: new #7 // class java/lang/StringBuilder //new StringBuilder
9: dup //复制操作数栈的栈顶值
10: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V //调用无参构造器初始化
13: ldc #2 // String hello
15: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: aload_2
19: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: astore_3
26: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
29: aload_3
30: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: return
我们可以看到对于final修饰String引用,在编译器就进行了优化,x+"world"直接优化成"helloworld"
java虚拟机(十四)--字节码指令的更多相关文章
- Java方法调用的字节码指令学习
Java1.8环境下,我们在编写程序时会进行各种方法调用,虚拟机在执行这些调用的时候会用到不同的字节码指令,共有如下五种: invokespecial:调用私有实例方法: invokestatic:调 ...
- JVM总结-虚拟机怎么执行字节码
1. JRE,JDK JRE : 包含运行 Java 程序的必需组件,Java 虚拟机+ Java 核心类库等. JDK : JRE + 一系列开发.诊断工具. 2. java字节码 编译器将 Ja ...
- [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式
前言简介 前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是 ...
- 深入理解java虚拟机(六)字节码指令简介
Java虚拟机指令是由(占用一个字节长度.代表某种特定操作含义的数字)操作码Opcode,以及跟随在其后的零至多个代表此操作所需参数的称为操作数 Operands 构成的.由于Java虚拟机是面向操作 ...
- 深入了解java虚拟机(JVM) 第十章 字节码指令
一.字节码指令的含义 Java字节码指令由一个字节长度的,代表某种特定操作含义的数字(操作码)以及其后的零至多个代表此操作所需参数(操作数).此外字节码指令是面向操作数栈的,这里操作数栈在功能上对应实 ...
- Java虚拟机-字节码指令
目录 字节码指令 字节码与数据类型 加载和存储指令 运算指令 类型转换指令 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指令 同步指令 字节码指令 Java虚拟机的 ...
- 【JVM源码解析】模板解释器解释执行Java字节码指令(上)
本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...
- 大话+图说:Java字节码指令——只为让你懂
前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...
- Java字节码指令
1. 简介 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码)以及跟随其后的零至多个代表此操作所需参数(称为操作数)而构成. 由于Java虚拟机采用面向操作数栈而不是寄存 ...
随机推荐
- Perl 数组
Perl 数组 Perl 数组一个是存储标量值的列表变量,变量可以是不同类型. 数组变量以 @ 开头.访问数组元素使用 $ + 变量名称 + [索引值] 格式来读取,实例如下: 实例 #!/usr/b ...
- thinkphp 切换数据库
除了在预先定义数据库连接和实例化的时候指定数据库连接外,我们还可以在模型操作过程中动态的切换数据库,支持切换到相同和不同的数据库类型.用法很简单, 只需要调用Model类的db方法,用法: 常州大理石 ...
- duilib教程之duilib入门简明教程15.自绘控件
在[2013 duilib入门简明教程 -- 复杂控件介绍 (13)]中虽然介绍了界面设计器上的所有控件,但是还有一些控件并没有被放到界面设计器上,还有一些常用控件duilib并没有提供(比如菜单控件 ...
- python支付宝页面扫码支付
一.介绍 基于网上一个支付宝pay.py封装了支付宝API的文件进行的,以下代码只支持网页扫码支付,手机端会提示调用支付宝支付 #pay文件代码 from datetime import dateti ...
- Linux课程---14、linux下lamp环境如何安装
Linux课程---14.linux下lamp环境如何安装 一.总结 一句话总结: 要按顺序安装,比如apache需要在php之前安装, 一.安装 gcc 编译器 二.卸载 rpm 安装的 http ...
- Mysql优化系列之查询性能优化前篇2
接前一篇,这一篇主要总结下几个经常要用的命令 命令一:explain+sql mysql> explain select * from servers; +----+-------------+ ...
- 初识OpenCV-Python - 008: 形态转换
本节学习了图片的形态转换,即利用函数和图像的前景色和背景色去侵蚀或者扩张图像图形. import cv2import numpy as npfrom matplotlib import pyplot ...
- vue表格之:summary-method="getSummaries"与show-summary(列求和)
//表格列求和 <el-table :summary-method="getSummaries" show-summary></el-table> getS ...
- 测试环境添加spark parcel 2.1步骤
1.先到http://archive.cloudera.com/spark2/parcels/2.1.0.cloudera1/ 下载需要的文件比如我linux版本需要是6的 hadoop6需要下载这些 ...
- 使用了@Slf4j log没有info的方法 .info()方法爆红或者log爆红
在springboot项目中,使用注解@Slf4j时,log变量不能用. 导包用的是 import lombok.extern.slf4j.Slf4j; <dependency> < ...