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虚拟机采用面向操作数栈而不是寄存 ...
随机推荐
- h5判断是否为iphonex
js移动端页面判断是否是iphoneX 转自https://blog.csdn.net/weixin_39924326/article/details/80352929 function isIPho ...
- layui点击按钮自动刷新页面问题
问题 <button class="layui-btn layui-btn-primary" onclick="findData()">查询< ...
- iOS开发之SceneKit框架--SCNNode.h
1.SCNNode简介 SCNNode是场景图的结构元素,表示3D坐标空间中的位置和变换,您可以将模型,灯光,相机或其他可显示内容附加到该元素.也可以对其做动画. 2.相关API简介 初始化方法 // ...
- vs2017 Visual Studio 离线安装方法
转自:http://www.jb51.net/softjc/539858.html 第一部分:离线下载安装文件 这里描述是包括所有版本,截图以下载VS2017社区版为例: ①登入VS官网下载页面,选择 ...
- Java工具之NotePad++使用技巧
按住Alt键 拖动鼠标左键 批量添加 如,等 批量添加逗号, 下面, 竖排 变 横排 ctrl + f 使用正则表达式 \r\n 替换换行符 使用:sql语句中的 过滤条件 in中,往往适合范围查找 ...
- Jumpserver-1.5.2 安装步骤
Jumpsever 是飞致云旗下的一块开源的堡垒机.在如今都在上云的趋势下,一款堡垒机非常重要. 官网:http://jumpserver.org/ GitHub:https://github.com ...
- vue 计算属性实现过滤关键词
效果 html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <m ...
- 设置Hadoop+Hbase集群pid文件存储位置
有时候,我们对运行几天或者几个月的hadoop或者hbase集群做停止操作,会发现,停止命令不管用了,为什么呢? 因为基于java开发的程序,想要停止程序,必须通过进程pid来确定,而hadoop和h ...
- 在页面上显示PDF
/// <summary> /// 读取PDF文件 /// </summary> /// <param name="fName">文件名称(可以 ...
- 【树剖】CF916E Jamie and Tree
好吧这其实应该不是树剖... 因为只要求子树就够了,dfs就好了 大概就是记录一个全局根root 多画几幅图会发现修改时x,y以root为根时的lca为以1为根时的lca(x,y),lca(root, ...