从字节码角度分析Byte类型变量b++和++b
1. 下面是一到Java笔试题:
public class Test2
{
public void add(Byte b)
{
b = b++;
}
public void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
System.out.print(a + " ");
add(b);
System.out.print(b + "");
}
}
2. 为方便分析起见,将打印的语句去掉,如下:
public void add(Byte b)
{
b = b++;
}
public void test()
{
Byte a = 127;
Byte b = 127;
add(++a);
add(b);
}
3. 将上述代码反编译,得到如下字节码:
public void add(java.lang.Byte);
Code:
0: aload_1
1: astore_2
2: aload_1
3: invokevirtual #2 // Method java/lang/Byte.byteValue:(
)B
6: iconst_1
7: iadd
8: i2b
9: invokestatic #3 // Method java/lang/Byte.valueOf:(B)
Ljava/lang/Byte;
12: dup
13: astore_1
14: astore_3
15: aload_2
16: astore_1
17: return public void test();
Code:
0: bipush 127
2: invokestatic #3 // Method java/lang/Byte.valueOf:(B)
Ljava/lang/Byte;
5: astore_1
6: bipush 127
8: invokestatic #3 // Method java/lang/Byte.valueOf:(B)
Ljava/lang/Byte;
11: astore_2
12: aload_0
13: aload_1
14: invokevirtual #2 // Method java/lang/Byte.byteValue:(
)B
17: iconst_1
18: iadd
19: i2b
20: invokestatic #3 // Method java/lang/Byte.valueOf:(B)
Ljava/lang/Byte;
23: dup
24: astore_1
25: invokevirtual #4 // Method add:(Ljava/lang/Byte;)V
28: aload_0
29: aload_2
30: invokevirtual #4 // Method add:(Ljava/lang/Byte;)V
33: return
}
4. 字节码很长,看着发怵,不用怕,我们将字节码分成两部分:add方法和test方法。
5. 我们先来看add方法:
add方法局部变量表
下标: 0 1 2 3
标记: this 形参Byte b Byte型临时变量tmp Byte型临时变量tmp2
值 : -128 -128 -127
public void add(java.lang.Byte);
Code:
0: aload_1 // 局部变量表中下标为1的引用型局部变量b进栈
1: astore_2 // 将栈顶数值赋值给局部变量表中下标为2的引用型局部变量tmp,栈顶数值出栈。
2: aload_1 // 局部变量表中下标为1的引用型局部变量b进栈
3: invokevirtual #2 // 自动拆箱,访问栈顶元素b,调用实例方法b.byteValue获取b所指Byte
// 对象的value值-128,并压栈
6: iconst_1 // int型常量值1进栈
7: iadd // 依次弹出栈顶两int型数值1(0000 0001)、-128(1000 0000)
//(byte类型自动转型为int类型)相加,并将结果-127(1000 0001)进栈
8: i2b // 栈顶int值-127(1000 0001)出栈,强转成byte值-127(1000 0001),并且结果进栈
9: invokestatic #3 // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
// 返回value值为-127的Byte对象的地址,并压栈
12: dup // 复制栈顶数值,并且复制值进栈
13: astore_1 // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量b,栈顶数值出栈。此时b为-127
14: astore_3 // 将栈顶数值赋值给局部变量表中下标为3的引用型局部变量tmp2,栈顶数值出栈。此时tmp2为-127
15: aload_2 // 局部变量表中下标为2的引用型局部变量tmp进栈,即-128入栈
16: astore_1 // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量b,栈顶数值出栈。此时b为-128
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
test方法局部变量表
下标: 0 1 2
标记: this 形参Byte a Byte型临时变量b
值 : -128 127
public void test();
Code:
0: bipush 127 // 将一个byte型常量值推送至操作数栈栈顶
2: invokestatic #3 // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
// 返回value值为127的Byte对象的地址,并压栈
5: astore_1 // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量a,栈顶数值出栈。此时a为127
6: bipush 127 // 将一个byte型常量值推送至操作数栈栈顶
8: invokestatic #3 // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
// 返回value值为127的Byte对象的地址,并压栈。这里需要说明一点,
// 由于Byte类缓存了[-128,127]之间的Byte对象,故当传入的实参byte相同时,
// 通过Byte.valueOf(byte)返回的对象是同一个对象,详见Byte源码。
11: astore_2 // 将栈顶数值赋值给局部变量表中下标为2的引用型局部变量b,栈顶数值出栈。此时b为127
12: aload_0 // 局部变量表中下标为0的引用型局部变量进栈,即this,加载this主要是为了下面通过this调用add方法。
13: aload_1 // 局部变量表中下标为1的引用型局部变量a进栈
14: invokevirtual #2 // 自动拆箱,访问栈顶元素a,调用实例方法a.byteValue获取a所指Byte
// 对象的value值127,并压栈
17: iconst_1 // int型常量值1进栈
18: iadd // 依次弹出栈顶两int型数值1(0000 0001)、127(0111 1111)
//(byte类型自动转型为int类型)相加,并将结果128(1000 0000)进栈
19: i2b // 栈顶int值128(1000 0000)出栈,强转成byte值-128(1000 0000),并且结果进栈
20: invokestatic #3 // 自动装箱:访问栈顶元素,作为函数实参传入静态方法Byte.valueOf(byte),
// 返回value值为-128的Byte对象的地址,并压栈
23: dup // 复制栈顶数值,并且复制值进栈
24: astore_1 // 将栈顶数值赋值给局部变量表中下标为1的引用型局部变量a,栈顶数值出栈。此时a为-128
25: invokevirtual #4 // 调用实例方法add:(Byte),传入的实参为栈顶元素,也即a的拷贝,前面已经分析过了,该调用不改变a的对象值
// 该实例方法的调用需要访问栈中的两个参数,一个是实参,也即a的拷贝,一个是在第12步入栈的this。
28: aload_0 // 局部变量表中下标为0的引用型局部变量进栈,即this,加载this主要是为了下面通过this调用add方法。
29: aload_2 // 局部变量表中下标为2的引用型局部变量b进栈
30: invokevirtual #4 // 调用实例方法add:(Byte),传入的实参为栈顶元素,也即b,前面已经分析过了,该调用不改变b的对象值
// 该实例方法的调用需要访问栈中的两个参数,一个是实参,也即b,一个是在第28步入栈的this。
33: return // 函数执行到最后,b所指对象的值没有改变,仍为127。
}
7. 综合以上分析,原问题的输出为-128 127
8. 小结:
通过以上分析,我们发现该题综合考察了Byte自动拆/装箱、Byte对象缓存、Java编译器对i=i++的特殊处理等等,相当有难度呀。
从字节码角度分析Byte类型变量b++和++b的更多相关文章
- 反编译字节码角度分析synchronized关键字的原理
1.synchronized介绍 synchronized是java关键字.JVM规范中,synchronized关键字用于在线程并发执行时,保证同一时刻,只有一个线程可以执行某个代码块或方法:同时还 ...
- 源码角度分析-newFixedThreadPool线程池导致的内存飙升问题
前言 使用无界队列的线程池会导致内存飙升吗?面试官经常会问这个问题,本文将基于源码,去分析newFixedThreadPool线程池导致的内存飙升问题,希望能加深大家的理解. (想自学习编程的小伙伴请 ...
- 字节码增强技术-Byte Buddy
本文转载自字节码增强技术-Byte Buddy 为什么需要在运行时生成代码? Java 是一个强类型语言系统,要求变量和对象都有一个确定的类型,不兼容类型赋值都会造成转换异常,通常情况下这种错误都会被 ...
- 从JDK源码角度看Byte
Java的Byte类主要的作用就是对基本类型byte进行封装,提供了一些处理byte类型的方法,比如byte到String类型的转换方法或String类型到byte类型的转换方法,当然也包含与其他类型 ...
- synchronized关键字所生成的字节码详细分析
在之前已经将如下这样的源文件对应的字节码文件完整的分析完了,如下: 这次再来写一个内容稍丰富一点的类,准备再来从头至尾的来分析一下,对其字节码的理解进一步巩固,如下: 然后用javap -verbos ...
- 从java字节码角度看线程安全性问题
先看代码: package com.roocon.thread.t3; public class Sequence { private int value; public int getNext(){ ...
- 从字节码来分析,i++与++i区别
++/-- 是一种特殊的算术运算符,在算术运算符中需要两个操作数来进行运算,而自增自减运算符是一个操作数 前缀自增(++a):先进行自增运算,再进行表达式运算: 后缀自增(a++):先进行表达式运算, ...
- 【synchronized锁】通过synchronized锁 反编译查看字节码指令分析synchronized关键字修饰方法与代码块的区别
前提: 首先要铺垫几个前置的知识: Java中的锁如sychronize锁是对象锁,Java对象头中具有标识位,当对象锁升级为重量级锁时,重量级锁的标识位会指向监视器monitor, 而每个Java对 ...
- 从源码角度分析 MyBatis 工作原理
一.MyBatis 完整示例 这里,我将以一个入门级的示例来演示 MyBatis 是如何工作的. 注:本文后面章节中的原理.源码部分也将基于这个示例来进行讲解.完整示例源码地址 1.1. 数据库准备 ...
随机推荐
- java基础-引用数据类型之一维数组(Array)
java基础-引用数据类型之一维数组(Array) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.数组的定义 1>.为什么需要数组 保存一个数据可以定义一个变量,如果要保 ...
- SFTP上传下载文件、文件夹常用操作
SFTP上传下载文件.文件夹常用操作 1.查看上传下载目录lpwd 2.改变上传和下载的目录(例如D盘):lcd d:/ 3.查看当前路径pwd 4.下载文件(例如我要将服务器上tomcat的日志文 ...
- 转:iOS绘制一个UIView
绘制一个UIView 绘制一个UIVIew最灵活的方式就是由它自己完成绘制.实际上你不是绘制一个UIView,你只是子类化了UIView并赋予子类绘制自己的能力.当一个UIVIew需要执行绘图操作的时 ...
- 2 Kafka Broker
Log的读写.删除流程---日志管理器(log manager)负责创建日志.获取日志.清理日志.所有的日志读写操作都交给具体的日志实例来完成. KafkaServer启动的时候,初始化三个类: Lo ...
- Shell记录-Shell命令(定时任务)
在Linux系统中, at 命令是针对仅运行一次的任务,循环运行的例行性计划任务,linux系统则是由 cron(crond) 这个系统服务来控制的.Linux 系统上面原本就有非常多的计划性工作,因 ...
- webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制
webapi框架搭建系列博客 在上一篇的webapi框架搭建-安全机制(三)-简单的基于角色的权限控制,某个角色拥有哪些接口的权限是用硬编码的方式写在接口上的,如RBAuthorize(Roles = ...
- COGS 5. P服务点设置
5. P服务点设置 http://www.cogs.pro/cogs/problem/problem.php?pid=5 ★★ 输入文件:djsc.in 输出文件:djsc.out 简单对 ...
- 2015/12/14 Python网络编程,TCP/IP客户端和服务器初探
一直不是很清楚服务器的定义,对于什么是服务器/客户端架构也只有一个模糊的感觉.最近开始学习,才明白一些什么服务器和客户端的关系. 所谓的服务器,就是提供服务的东西,它是一个硬件或者软件,可以向一个或者 ...
- Fiddler 使用
一.模拟post请求 User-Agent: FiddlerContent-Type: application/json; charset=utf-8Content-Length: 138Conten ...
- IE6 下 DD_belatedPNG 引发的血案
群里一朋友Q我,说遇到兼容性问题了,我说为何不用jQuery呢(因为他们公司要求尽量js写).他说用了,还是有问题,IE6下不行,其他都行.然后他发我代码,我一开始真以为是兼容性问题,比如数组对象最后 ...