java从字节码角度解析案例(转)

本文来自于参考博客

1. 下面是一到Java笔试题:

 1 public class Test2
 2 {
 3     public void add(Byte b)
 4     {
 5         b = b++;
 6     }
 7     public void test()
 8     {
 9         Byte a = 127;
10         Byte b = 127;
11         add(++a);
12         System.out.print(a + " ");
13         add(b);
14         System.out.print(b + "");
15     }
16 }

2. 为方便分析起见,将打印的语句去掉,如下:

 1     public void add(Byte b)
 2     {
 3         b = b++;
 4     }
 5     public void test()
 6     {
 7         Byte a = 127;
 8         Byte b = 127;
 9         add(++a);
10         add(b);
11     }

3. 将上述代码反编译,得到如下字节码:

 1 public void add(java.lang.Byte);
 2     Code:
 3        0: aload_1
 4        1: astore_2
 5        2: aload_1
 6        3: invokevirtual #2                  // Method java/lang/Byte.byteValue:(
 7 )B
 8        6: iconst_1
 9        7: iadd
10        8: i2b
11        9: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
12 Ljava/lang/Byte;
13       12: dup
14       13: astore_1
15       14: astore_3
16       15: aload_2
17       16: astore_1
18       17: return
19
20 public void test();
21     Code:
22        0: bipush        127
23        2: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
24 Ljava/lang/Byte;
25        5: astore_1
26        6: bipush        127
27        8: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
28 Ljava/lang/Byte;
29       11: astore_2
30       12: aload_0
31       13: aload_1
32       14: invokevirtual #2                  // Method java/lang/Byte.byteValue:(
33 )B
34       17: iconst_1
35       18: iadd
36       19: i2b
37       20: invokestatic  #3                  // Method java/lang/Byte.valueOf:(B)
38 Ljava/lang/Byte;
39       23: dup
40       24: astore_1
41       25: invokevirtual #4                  // Method add:(Ljava/lang/Byte;)V
42       28: aload_0
43       29: aload_2
44       30: invokevirtual #4                  // Method add:(Ljava/lang/Byte;)V
45       33: return
46 }

4. 字节码很长,看着发怵,不用怕,我们将字节码分成两部分:add方法和test方法。

5. 我们先来看add方法:

 1 add方法局部变量表
 2 下标:  0         1                2                    3
 3 标记: this   形参Byte b   Byte型临时变量tmp     Byte型临时变量tmp2
 4 值  :          -128             -128                  -127
 5 public void add(java.lang.Byte);
 6     Code:
 7        0: aload_1          // 局部变量表中下标为1的引用型局部变量b进栈
 8        1: astore_2         // 将栈顶数值赋值给局部变量表中下标为2的引用型局部变量tmp,栈顶数值出栈。
 9        2: aload_1           // 局部变量表中下标为1的引用型局部变量b进栈
10        3: invokevirtual #2 // 自动拆箱,访问栈顶元素b,调用实例方法b.byteValue获取b所指Byte
11                            // 对象的value值-128,并压栈
12        6: iconst_1           // int型常量值1进栈
13        7: iadd               // 依次弹出栈顶两int型数值1(0000 0001)、-128(1000 0000)
14                            //(byte类型自动转型为int类型)相加,并将结果-127(1000 0001)进栈
15        8: i2b               // 栈顶int值-127(1000 0001)出栈,强转成byte值-127(1000 0001),并且结果进栈
16        9: invokestatic  #3 // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
17                            // 返回value值为-127的Byte对象的地址,并压栈
18       12: dup               // 复制栈顶数值,并且复制值进栈
19       13: astore_1           // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量b,栈顶数值出栈。此时b为-127
20       14: astore_3           // 将栈顶数值赋值给局部变量表中下标为3的引用型局部变量tmp2,栈顶数值出栈。此时tmp2为-127
21       15: aload_2           // 局部变量表中下标为2的引用型局部变量tmp进栈,即-128入栈
22       16: astore_1         // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量b,栈顶数值出栈。此时b为-128
23       17: return

总结一下上述过程,核心步骤为b = b++;分为三步:参考:http://blog.csdn.net/brooksychen/article/details/1624753

①把变量b的值取出来,放在一个临时变量里(我们先记作tmp);

②把变量b的值进行自加操作;

③把临时变量tmp的值作为自增运算前b的值使用,在本题中就是给变量b赋值。

到此可得出结论,add方法只是个摆设,没有任何作用,不修改实参的值。

6. 搞懂了add方法,我们接下来分析test方法:

这里需要说明两点:

(1)由于Byte类缓存了[-128,127]之间的Byte对象,故当传入的实参byte相同时,通过Byte.valueOf(byte)返回的对象是同一个对象,详见Byte源码。

(2)如果是实例方法(非static),那么局部变量表的第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中通过this访问。详见:http://wangwengcn.iteye.com/blog/1622195

 1 test方法局部变量表
 2 下标:  0         1                2
 3 标记: this   形参Byte a   Byte型临时变量b
 4 值  :          -128             127
 5 public void test();
 6     Code:
 7        0: bipush        127        // 将一个byte型常量值推送至操作数栈栈顶
 8        2: invokestatic  #3        // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
 9                                 // 返回value值为127的Byte对象的地址,并压栈
10        5: astore_1                // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量a,栈顶数值出栈。此时a为127
11        6: bipush        127        // 将一个byte型常量值推送至操作数栈栈顶
12        8: invokestatic  #3        // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
13                                 // 返回value值为127的Byte对象的地址,并压栈。这里需要说明一点,
14                                 // 由于Byte类缓存了[-128,127]之间的Byte对象,故当传入的实参byte相同时,
15                                 // 通过Byte.valueOf(byte)返回的对象是同一个对象,详见Byte源码。
16       11: astore_2                // 将栈顶数值赋值给局部变量表中下标为2的引用型局部变量b,栈顶数值出栈。此时b为127
17       12: aload_0                // 局部变量表中下标为0的引用型局部变量进栈,即this,加载this主要是为了下面通过this调用add方法。
18       13: aload_1                // 局部变量表中下标为1的引用型局部变量a进栈
19       14: invokevirtual #2      // 自动拆箱,访问栈顶元素a,调用实例方法a.byteValue获取a所指Byte
20                                 // 对象的value值127,并压栈
21       17: iconst_1                // int型常量值1进栈
22       18: iadd                    // 依次弹出栈顶两int型数值1(0000 0001)、127(0111 1111)
23                                 //(byte类型自动转型为int类型)相加,并将结果128(1000 0000)进栈
24       19: i2b                    // 栈顶int值128(1000 0000)出栈,强转成byte值-128(1000 0000),并且结果进栈
25       20: invokestatic  #3      // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
26                                 // 返回value值为-128的Byte对象的地址,并压栈
27       23: dup                    // 复制栈顶数值,并且复制值进栈
28       24: astore_1                // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量a,栈顶数值出栈。此时a为-128
29       25: invokevirtual #4      // 调用实例方法add:(Byte),传入的实参为栈顶元素,也即a的拷贝,前面已经分析过了,该调用不改变a的对象值
30                                 // 该实例方法的调用需要访问栈中的两个参数,一个是实参,也即a的拷贝,一个是在第12步入栈的this。
31       28: aload_0                // 局部变量表中下标为0的引用型局部变量进栈,即this,加载this主要是为了下面通过this调用add方法。
32       29: aload_2                // 局部变量表中下标为2的引用型局部变量b进栈
33       30: invokevirtual #4      // 调用实例方法add:(Byte),传入的实参为栈顶元素,也即b,前面已经分析过了,该调用不改变b的对象值
34                                 // 该实例方法的调用需要访问栈中的两个参数,一个是实参,也即b,一个是在第28步入栈的this。
35       33: return                // 函数执行到最后,b所指对象的值没有改变,仍为127。
36 }

7. 综合以上分析,原问题的输出为-128 127

8. 总结:局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量, 虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程的,如果是实例方法(非static),那么局部变量表的第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中通过this访问。

    Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指-操作数栈。和局部变量区一样,操作数栈也是被组织成一个以字长为单位的数组。但是和前者不同的是,它不是通过索引来访问,而是通过标准的栈操作—压栈和出栈—来访问的。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。

参考:https://www.cnblogs.com/nailperry/p/4780354.html

   https://www.iteye.com/blog/wangwengcn-1622195

   https://blog.csdn.net/brooksychen/article/details/1624753

原文地址:https://www.cnblogs.com/jianglinliu/p/11840234.html

时间: 2024-10-10 05:52:41

java从字节码角度解析案例(转)的相关文章

聊聊Java的字节码

巴山楚水凄凉地,二十三年弃置身.怀旧空吟闻笛赋,到乡翻似烂柯人.沉舟侧畔千帆过,病树前头万木春.今日听君歌一曲,暂凭杯酒长精神. 一.什么是Java字节码? 借用Algorithm(4th)节选:它是程序的一种低级表示,可以运行于Java虚拟机上.将程序抽象成字节码可以保证Java程序在各种设备上的运行.OK,这个回答可以让我们回到程序执行上来看下一个问题: 二.程序是怎么执行的? 学习过计算机的同学都应该深恶痛绝一门课程--编译原理,这门课程的主要目的就是向你解释你所见所写的代码的执行过程.我

Java虚拟机-字节码执行引擎

概述 Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade).不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行. 运行时帧栈结构 栈帧(Stack Frame)支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量.操作数栈.动态连接和方法返回地址等信息.方法调用开始到执行完成,对应这一个帧栈在虚拟机栈里面入栈和出栈的过程. 一个线程中

Tomcat部署Web应用方面的一些问题(多Tomcat、指定Java、字节码替换)

这篇博文操作的系统环境为CentOS系统,目标是在该系统中部署一个Tomcat以及跑在该Tomcat上的一个Java Web应用.部署的系统环境有限制,主要表现在:该主机上已部署Tomcat,新部署的Tomcat应不影响原Tomcat及相应应用的访问:此外,系统上已安装某一版本的Java,已设置JAVA_HOME等环境变量,而当前应用依赖另一版本的Java, 因此需要在不影响原应用.环境变量配置的基础上解决依赖问题. Tomcat安装 安装前需准备好Java环境,配置好JAVA_HOME环境变量

JAVA的字节码技术

1.什么是字节码? 字节码 byteCode JVM能够解释执行的.java程序的归宿,但是从规范上来讲和Java已没有任何关系了.一些动态语言也可以编译成字节码在JVM上运行.字节码就相当于JVM上的汇编语言. 2.字节码的一些特点标准JVM使用的堆栈:有些虚拟机使用寄存器.例如安卓虚拟机.标准JVM使用一个字节的指令.理论上256个指令,目前已经使用了200+. 3.字节码的用途 a.静态检查 b.调试/热切换/诊断工具 c.在JVM上的新语言 d.AOP.ORM e.Mock 尤其是Fau

Java虚拟机--字节码指令集

字节码指令集简介: Java虚拟机的指令由一个字节长度的.代表着某种特定操作含义的操作码(Opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成.虚拟机中许多指令并不包含操作数,只有一个操作码. do { 自动计算PC寄存器以及从PC寄存器的位置取出操作码; if (存在操作数) 取出操作数; 执行操作码所定义的操作 } while (处理下一次循环)

java的字节码bytecode

字节码名字的由来 字节码以一个字节即8bit为最小单位储存:字节码是java程序编译后的结果:字节码是一组8位字节为基础单位的二进制流 Java从源文件到执行的过程. 如何阅读JAVA字节码 原文地址:https://www.cnblogs.com/shengulong/p/11711423.html

Android AsyncTask 从源码角度解析其原理和用法

AsyncTask简介: 众所周知知道,Android UI是线程不安全的,如果要想在子线程中更新UI操作,必须使用Android的异步消息处理机制.当然我们自己可以实现一个Handler+Message消息处理机制来在子线程中更新UI操作.有时候觉得自己写这个异步消息处理机制很麻烦有木有??不过庆幸的是,Android 给我们实现了这么一套异步消息处理机制,我们直接拿来用就是了,从而 AsyncTask就诞生了.AsyncTask用于Android的异步消息 处理机制,来实现子线程和UI线程间

从源码角度解析Netty的React模式是如何工作的

Netty 支持多种实现方式,比如nio,epoll 等,本文以nio的实现方式进行讲解. 1.EventLoop : 事件循环看,简单来说就是一个死循环监听事件,如果事件来了,处理掉.通常做法就是开启一个独立线程,一直循环. 伪代码: while (queue.waitForMessage()) { queue.processNextMessage();} 2.EventLoopGroup: 一组(多个)事件循环. 3.bossGroup 和 workGroup EventLoopGroup

MySQL半一致性读原理解析-从源码角度解析

1.什么是半一致性读 A type of read operation used for UPDATE statements, that is a combination of read committed and consistent read. When an UPDATE statement examines a row that is already locked, InnoDB returns the latest committed version to MySQL so that