一道String字符串比较问题引发的字节码分析

public class a {
public static void main(String[] args)throws Exception{ }
public static void aa(){
String s1="a";//内存在方法区的常量池
String s2="b";//内存在方法区的常量池
String s12 = "ab";//内存在方法区的常量池
String s3 = s1 + s2;//s3的内存所在???
p(s3==s12);//false
}
public static void bb(){
String s1="a"+"b";//s1的内存所在???
String s2 = "ab";//内存在方法区的常量池
p(s1==s2);//true
}
public static void p(Object obj){
System.out.println(obj);
}
}

这是我们经常碰到的烦人的String比较问题,要得到答案,就要弄清楚aa()方法中的s3的内存在哪里?,和bb()方法中的s1的内存在哪里?

不多说,贴上a.class文件反编译的字节码指令:

首先是 aa()方法:

public static void aa();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=0 共4个本地变量空间
0: ldc #3 // String a   将字符串"a"从常量池中推送至栈顶
2: astore_0 将栈顶引用类型(即字符串"a")存入第一个本地变量
3: ldc #4 // String b 将字符串"b"从常量池推送至栈顶
5: astore_1 将栈顶引用类型(即字符串"b")存入第二个本地变量
6: ldc #5 // String ab 将字符串"ab"从常量池推送至栈顶
8: astore_2 将栈顶引用类型(即字符串"ab")存入第三个本地变量
9: new #6 // class java/lang/StringBuilder 创建StringBuilder对象sb,并将引用值压入栈顶
12: dup 复制栈顶数值,并将复制值压入栈顶
13: invokespecial #7 // Method java/lang/StringBuilder. 调用对象的初始化方法
"<init>":()V
16: aload_0 将第一个本地变量(即字符串"a")推送至栈顶
17: invokevirtual #8 // Method java/lang/StringBuilder. 调用实例方法sb.append("a");
append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_1 将第二个本地变量(即字符串"b")推送至栈顶
21: invokevirtual #8 // Method java/lang/StringBuilder. 调用实例方法sb.append("b");
append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #9 // Method java/lang/StringBuilder. 调用实例方法sb.toString(),并将结果【Java堆地址】放在栈顶
toString:()Ljava/lang/String;
27: astore_3 将栈顶引用类型(即堆地址)存入第四个本地变量
28: aload_3 将第四个本地变量(即堆地址)推送至栈顶
29: aload_2 将第三个本地变量(即字符串"ab")推送至栈顶
30: if_acmpne 37 比较栈顶两引用数值,结果不同跳转(当然不同啦)
33: iconst_1
34: goto 38
37: iconst_0 将int类型 0 压入栈顶
38: invokestatic #10 // Method java/lang/Boolean.valueO 调用静态方法Boolean.valueOf();实现基本数据类型->包装类型自动转换
f:(Z)Ljava/lang/Boolean;
41: invokestatic #11 // Method p:(Ljava/lang/Object;)V 调用静态方法p(false);//输出false
44: return 从当前方法返回void

针对其中的一些解释:(下面的数字是字节码偏移量)

24       为何在sb.toString()我说的是【堆地址】,大家看源码就知道了。

//这是StringBuilder的源码,返回的是堆上的字符串地址
public String toString() {
return new String(value, 0, count);
}

所以在aa()方法中,s3的内存其实在Java堆上,s12在方法区的常量池上,所以两者不相等。

37      boolean到底分配几个字节,在这里大家可以看到。

如果为true,编译器翻译的字节码是iconst_1,意思将int类型1存入栈顶,所以单个引用boolean值时,分配4个字节,和int相同。(数组boolean没测试,不清楚)

如果为false,编译器翻译的字节码是iconst_0,意思将int类型0存入栈顶。

38      在这里我们还能看到自动类型转换的身影,这里是基本数据类型boolean->包装类Boolean的自动类型转换,实际调用的就是Boolean.valueOf()静态方法,这是因为下面的p()方法里面需要的是Object引用类型,所以进行了自动类型转换。

然后是 bb()方法:

public static void bb();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=0 两个本地变量空间
0: ldc #5 // String ab 将字符串"ab"从常量池中推送至栈顶
2: astore_0 将栈顶引用类型(字符串"ab")存入第一个本地变量
3: ldc #5 // String ab 将字符串"ab"从常量池中推送至栈顶
5: astore_1 将栈顶引用类型(字符串"ab")存入第一个本地变量
6: aload_0 将第一个本地变量("ab")推送至栈顶
7: aload_1 将第二个本地变量("ab")推送至栈顶
8: if_acmpne 15 比较栈顶两引用类型数值,结果不同跳转(这里当然相同啦)
11: iconst_1 将int类型 1 推送至栈顶
12: goto 16 无条件跳转到16字节码偏移量
15: iconst_0
16: invokestatic #10 // Method java/lang/Boolean.valueO 调用静态方法Boolean.valueOf();并将返回的Boolean类型的true压入栈顶
f:(Z)Ljava/lang/Boolean;
19: invokestatic #11 // Method p:(Ljava/lang/Object;)V 调用静态方法p(true);输出true
22: return 从当前方法返回void

针对其中的一些解释:(下面的数字是字节码偏移量)

0           大家看到了吧,编译器看到String a="aa"+"bb";会自动合并,将"aabb"存入常量池,并返回地址。所以答案为true。

----------------------------------------------------新添加内容分割线-----------------------------------------------------------------

我在这篇博文中JVM-绘图展现方法内部字节码执行具体画了上面的bb()方法的字节码执行流程,如果大家对执行过程感兴趣,欢迎大家访问。

JVM-String比较-字节码分析的更多相关文章

  1. Java并发编程原理与实战八:产生线程安全性问题原因(javap字节码分析)

    前面我们说到多线程带来的风险,其中一个很重要的就是安全性,因为其重要性因此,放到本章来进行讲解,那么线程安全性问题产生的原因,我们这节将从底层字节码来进行分析. 一.问题引出 先看一段代码 packa ...

  2. Java字节码分析

    目录 Java字节码分析 查看字节码详细内容 javap 实例分析 Java字节码分析 对于源码的效率,但从源码来看有时无法分析出准确的结果,因为不同的编译器版本可能会将相同的源码编译成不同的字节码, ...

  3. 通过字节码分析this关键字以及异常表的重要作用

    在之前的字节码分析中缺少对异常的介绍,这次主要来对字节码异常表相关的东东进行一个学习,下面先来编写一个相关异常的小程序: 接着编译来看用javap -verbose来查看一下它的字节码信息: xion ...

  4. <JVM中篇:字节码与类的加载篇>04-再谈类的加载器

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  5. <JVM中篇:字节码与类的加载篇>03-类的加载过程(类的生命周期)详解

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  6. 通过字节码分析java中的switch语句

    在一次做题中遇到了switch的问题,由于对switch执行顺序的不了解,在这里简单的通过字节码的方式理解一下switch执行顺序(题目如下): public class Ag{ static pub ...

  7. <JVM中篇:字节码与类的加载篇>02-字节码指令集

    笔记来源:尚硅谷JVM全套教程,百万播放,全网巅峰(宋红康详解java虚拟机) 同步更新:https://gitee.com/vectorx/NOTE_JVM https://codechina.cs ...

  8. JVM总括三-字节码、字节码指令、JIT编译执行

    JVM总括三-字节码.字节码指令.JIT编译执行 目录:JVM总括:目录 java文件编译后的class文件,java跨平台的中间层,JVM通过对字节码的解释执行(执行模式,还有JIT编译执行,下面讲 ...

  9. Python_Tips[2] -> 函数延后估值及字节码分析

    函数延后估值及字节码分析 在一个循环中定义了函数 f 但是并未对其进行调用,在循环结束后调用,此时i值为3故最终3个函数输出均为9.而非1, 4, 9. 这是由于在定义闭包函数 f 时,传入变量 i, ...

随机推荐

  1. [SDK2.2]SQL Azure (13) Azure的两种关系型数据库服务:SQL Azure与SQL Server VM的不同

    <Windows Azure Platform 系列文章目录> 如果熟悉Windows Azure平台的用户不难发现,对于SQL Server数据库来说,微软提供了两种服务,分别是: -W ...

  2. .Net中List<T> 泛型转成DataTable、DataSet

    在开发过程过程中有时候需要将List<T>泛型转换成DataTable.DataSet,可以利用反射机制将DataTable的字段与自定义类型的公开属性互相赋值. 1.List<T& ...

  3. 解决ora-01652无法通过128(在temp表空间中)扩展temp段的过程

    解决ora-01652无法通过128(在temp表空间中)扩展temp段的过程 昨天开发人员跟我说,执行一个sql语句后,大约花了10分钟,好不容易有一个结果,但是报了一个ora-01652错误,查阅 ...

  4. KnockoutJS 3.X API 第七章 其他技术(5) 使用其他事件处理程序

    在大多数情况下,数据绑定属性提供了一种干净和简洁的方式来绑定到视图模型. 然而,事件处理是一个常常会导致详细数据绑定属性的领域,因为匿名函数通常是传递参数的推荐技术. 例如: <a href=& ...

  5. 面向对象设计之SRP(单一职责)原则

    SRP设计原则面向对象类设计的第一个原则,最优先考虑的因素 一个类应该有且仅有一个职责.所谓一个类的职责是指引起该类变化的原因,如果一个类具有一个以上的职责,那么就会有多个不同的原因 引起该类变化,其 ...

  6. Jetty Maven Plugin配置

    官方文档:http://www.eclipse.org/jetty/documentation/current/jetty-maven-plugin.html#maven-config-https 1 ...

  7. SSISDB5:Parameter

    Parameter 是Package 提供给外界的接口,通过传递不同的Parameter value,能够动态控制 Package 执行不同的Task或container,产生不同的结果. 一,Par ...

  8. EntityFramework之原始查询如何查询未映射的值,你又知道多少?

    前言 今天又倒腾了下EF,如题所示,遇到了一些问题,并最终通过尝试找到了解决方案,可能不是最终的解决方案,若你有更好的解决方案,望告知,同时通过阅读此文,定让你收获不少. 引入 当我们查询时一直是中规 ...

  9. No row with the given identifier exists:

    最近在弄一个后台项目,有用到hibernate操作数据库.写hql语句表一对一关联查询的时候报这个错误.受到了csdn上一篇博客的启发,解决了我的问题.他的博客地址:http://blog.csdn. ...

  10. JS验证控件jQuery Validate

    jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单,同时提供了大量的定制选项,满足应用程序各种需求.该插件捆绑了一套有用的验证方法,包括 URL 和电子邮件验证 ...