字节码指令其实是很重要的,在之前学习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虚拟机(十四)--字节码指令的更多相关文章

  1. Java方法调用的字节码指令学习

    Java1.8环境下,我们在编写程序时会进行各种方法调用,虚拟机在执行这些调用的时候会用到不同的字节码指令,共有如下五种: invokespecial:调用私有实例方法: invokestatic:调 ...

  2. JVM总结-虚拟机怎么执行字节码

    1. JRE,JDK JRE : 包含运行 Java 程序的必需组件,Java 虚拟机+ Java 核心类库等. JDK :  JRE + 一系列开发.诊断工具. 2. java字节码 编译器将 Ja ...

  3. [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式

      前言简介   前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明 想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是 ...

  4. 深入理解java虚拟机(六)字节码指令简介

    Java虚拟机指令是由(占用一个字节长度.代表某种特定操作含义的数字)操作码Opcode,以及跟随在其后的零至多个代表此操作所需参数的称为操作数 Operands 构成的.由于Java虚拟机是面向操作 ...

  5. 深入了解java虚拟机(JVM) 第十章 字节码指令

    一.字节码指令的含义 Java字节码指令由一个字节长度的,代表某种特定操作含义的数字(操作码)以及其后的零至多个代表此操作所需参数(操作数).此外字节码指令是面向操作数栈的,这里操作数栈在功能上对应实 ...

  6. Java虚拟机-字节码指令

    目录 字节码指令 字节码与数据类型 加载和存储指令 运算指令 类型转换指令 对象创建与访问指令 操作数栈管理指令 控制转移指令 方法调用和返回指令 异常处理指令 同步指令 字节码指令 Java虚拟机的 ...

  7. 【JVM源码解析】模板解释器解释执行Java字节码指令(上)

    本文由HeapDump性能社区首席讲师鸠摩(马智)授权整理发布 第17章-x86-64寄存器 不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Archit ...

  8. 大话+图说:Java字节码指令——只为让你懂

    前言 随着Java开发技术不断被推到新的高度,对于Java程序员来讲越来越需要具备对更深入的基础性技术的理解,比如Java字节码指令.不然,可能很难深入理解一些时下的新框架.新技术,盲目一味追新也会越 ...

  9. Java字节码指令

    1. 简介 Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的数字(称为操作码)以及跟随其后的零至多个代表此操作所需参数(称为操作数)而构成. 由于Java虚拟机采用面向操作数栈而不是寄存 ...

随机推荐

  1. python 基本常用数据类型

    #字典类型 result={1:2222,2:2221111}; result.items();#获取字典中所有元素 result.keys();#获取字典的key result.values();# ...

  2. 日志框架一logback配置和使用

    把logback或者log4j放在src/main/resources下,Spring容器就可以自动加载日志文件. 前言 Logback是由log4j创始人设计的又一个开源日志组件, 比log4j的性 ...

  3. Android Support 包的作用、用法

    1, Android Support V4, V7, V13是什么?本质上就是三个java library. 2,  为什么要有support库?如果在低版本Android平台上开发一个应用程序,而应 ...

  4. ie9 jscript7 内存不足 页面无响应

    花了我差不多一天时间 我是加载一个datagrid ,多表联查,查询几遍(不一定,又是1遍就死了)后 就卡死了...后台日志都是过的.... 后来我发现数据库某个表的数据很多有一模一样的两条,把一份删 ...

  5. 3列滚动抽奖 jquery.slotmachine

    效果图: 需引入js文件: <script src="js/jquery-3.2.0.js"></script> <script src=" ...

  6. <小白学技术>将python脚本导出为exe可执行程序

    1.简介(为啥需要导出为exe可执行程序) python写完的程序靠命令来执行,显得太专业,不符合python简单的特点(好吧,主要是太low) 代码给别人执行,别人没有你的python库也没法用(双 ...

  7. selenium基础(元素定位)

    selenium的帮助文档: https://selenium-python.readthedocs.io/api.html#module-selenium.common.exceptions 目前支 ...

  8. 控制类名(className 属性)设置或返回class属性

    控制类名(className 属性) className 属性设置或返回元素的class 属性. 语法: object.className = classname 作用: 1.获取元素的class 属 ...

  9. 串口通信中,QString 、QByteArray 转化需要注意的问题

    在做串口通信的时候,其中犯了一个错误.在此记录一下:QT中串口通信接到收据和发送数据的接口如下: QByteArray QIODevice::readAll()//接受数据 qint64 QIODev ...

  10. C++ 系列:typedef 和 #define 的区别

    总结一下typedef和#define的区别 1.概念 #define 它在编译预处理时进行简单的替换,不作正确性检查.它是预处理指令. typedef 它在自己的作用域内给一个已经存在的类型一个别名 ...