本篇文章教大家IDEA中查看字节码的三个方法 以及 jdk对字符串拼接、自动装箱和拆箱的操作过程。
首先要知道jdk、jre、jvm三者之间的关系:
JDK:Java 开发工具包,同时还包含了编译java源码的编译器javac,供开发使用。 JRE:Java 运行时环境,一些基本类库,供运行使用。 JVM:Java 虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并针对不同操作系统向上的 API 完成动作。
Java程序的大致执行流程如下:
java程序执行流程
所以说,jvm 是 Java 能够跨平台的核心。
新建一个Test.java
文件,并编译。下面进入正题,介绍三种方法查看 字节码。
javap是jdk自带的一个反汇编工具,可用于查看编译后的字节码。
在编译完成后,定位到你的 .class
文件
但是要看这个字节码,就很麻烦,需要先编译。每次都要找到这个.class
文件,然后输入 javap -c xxx.class
那有什么便捷的方法?
当然是有的。
打开 Settings
——》External Tools
name:插件的名字,我写了 MyjavapProgram:插件路径,我这里是 E:\JDK1.8\bin\javap.exe,按照你本地路径写即可。Arguments:参数,直接写 -c FileNameWithoutExtension.classWorking directory:class的输出目录,直接写 OutputPath\FileDirRelativeToSourcepath
点击 OK ,然后选中 你的 .java
源文件,点击 Tools
——》External Tools
——》Myjavap
以上就可以很方便地看到控制台输出了字节码。
点击一下你的 .java
源文件 ,然后 点击菜单栏 View
——》Show byteCode
然后就会弹出一个字节码的窗口。
在插件市场搜索 jclasslib
,点击安装。
重启IDEA。
菜单栏 View
——》Show Bytecode With jclasslib
下面说一下字节码的含义。
更多字节码的指令解释可参考:https://blog.csdn.net/qq_31407255/article/details/88978630
我本地的 Test.java
:
public class Test {
public static void main(String[] args) {
String a = "I am ";
String b = "HaC";
String c = a + b;
String d = "I am " + "HaC";
System.out.println(c == d); //false
Integer i =100; //装箱
int j = i; //拆箱
System.out.println(i == j); //true
}
}
这个是使用jclasslib的字节码:
0 ldc #2 <I am > # JVM采用ldc指令将常量压入栈中
2 astore_1 # 将栈顶引用类型值保存到局部变量1中,即a
3 ldc #3 <HaC>
5 astore_2
6 new #4 <java/lang/StringBuilder> # 创建新的对象实例,即c
9 dup # 复制栈顶一个字长的数据,将复制后的数据压栈。
10 invokespecial #5 <java/lang/StringBuilder.<init>> # 编译时方法绑定调用方法,编译时调用StringBuilder,相当于 new StringBuilder("I am "),下同
13 aload_1 # 从局部变量1,即对new StringBuilder("I am ")的引用,装载引用类型值入栈。
14 invokevirtual #6 <java/lang/StringBuilder.append> # 这里调用StringBuilder的appen方法,运行时进行拼接
17 aload_2
18 invokevirtual #6 <java/lang/StringBuilder.append>
21 invokevirtual #7 <java/lang/StringBuilder.toString> # 最后调用 toString方法,即 "I am HaC".toString
24 astore_3
25 ldc #8 <I am HaC> # 这里jvm直接优化了,把 d 变成了"I am HaC"
27 astore 4
29 getstatic #9 <java/lang/System.out>
32 aload_3
33 aload 4
35 if_acmpne 42 (+7)
38 iconst_1
39 goto 43 (+4)
42 iconst_0
43 invokevirtual #10 <java/io/PrintStream.println>
46 bipush 100
48 invokestatic #11 <java/lang/Integer.valueOf> # Integer i =100;装箱,即等于Integer i =Integer.valueOf(100);
51 astore 5
53 aload 5
55 invokevirtual #12 <java/lang/Integer.intValue> # int j = i; 拆箱,即等于 int j = i.intValue();
58 istore 6
60 getstatic #9 <java/lang/System.out>
63 aload 5
65 invokevirtual #12 <java/lang/Integer.intValue> # Integer和int比较, Integer自动调用intValue方法,即拆箱
68 iload 6
70 if_icmpne 77 (+7)
73 iconst_1
74 goto 78 (+4)
77 iconst_0
78 invokevirtual #10 <java/io/PrintStream.println>
81 return
所以说,
String c = a + b;
String d = "I am " + "HaC";
System.out.println(c == d); //false
c
是使用了 StringBuilder
进行拼接,最后返回的是堆的内存引用,而 d
是 jvm优化后,直接指向常量池的 "I am HaC"
,两者是不相等的。
而当 Integer对象 和 int 基本类型 比较的时候,Integer会自动拆箱,转化成 int 类型,比较的是值,两者自然相等。
Integer i =100; //装箱
int j = i; //拆箱
System.out.println(i == j); //true