1:IDEA 如何查看字节码文件
对于 IDEA 查看字节码,有人喜欢每次使用的时候在命令行敲命令,我觉得比较麻烦,于是添加工具组来实现: 参考:https://blog.csdn.net/xqt1028/article/details/106366375
1.1:通过File–>Settings… 打开 External Tools 窗口
1.2:点击图中的“+”,填写内容
Program:javap.exe的路径,这里要使用绝对路径(jdk安装包的bin目录下)。 Arguments:-v
F
i
l
e
N
a
m
e
W
i
t
h
o
u
t
E
x
t
e
n
s
i
o
n
FileNameWithoutExtension
FileNameWithoutExtension.class Working directory:
O
u
t
p
u
t
P
a
t
h
OutputPath
OutputPath/
F
i
l
e
D
i
r
R
e
l
a
t
i
v
e
T
o
S
o
u
r
c
e
p
a
t
h
FileDirRelativeToSourcepath
FileDirRelativeToSourcepath
1.3:选择一个java文件,右键选择 External Tools–>查看字节码(这是自己定义的名),即可查看字节码。
2:查看字节码文件(需要 jvm 基础)
虚拟机栈中是一个个栈帧,对应着调用的一个个 java 方法,但是栈帧中还是有内容的,可以分成以下几部分:
- Local Variable Table:本地变量(局部变量)表,方法内部使用的,参数也算在内,以变量槽为最小单位,每个槽32位的内存空间。
局部变量表主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类原始数据类型、对象引用(reference),以及returnAddress类型。 局部变量表所需的容量大小在编译期就可以被完全确定下来,并保存在方法的Code属性中。 - Operand Stack:操作数栈(表达式栈)
对于long的处理(store and load),多数虚拟机的实现都是原子的 局部变量,没必要加volatile,线程私有的。 操作数栈所需的容量大小在编译期就可以被完全确定下来,并保存在方法的Code属性中。通过标准的出入栈完成数据访问 32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。 - Dynamic Linking:动态链接,指向常量池的符号链接,如果没有解析,就去动态解析
- return address:返回值地址
a() -> b(),方法a调用了方法b, b方法的返回值放在什么地方
2.1:基本的字节码
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}
注意:字节码的一些指令需要自己去了解学习,这里不再花费大的篇幅去介绍。
2.2:带有异常的字节码
public class Demo3_11_1 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
}
}
}
我们主要查看 main 方法对应的字节码;
2.3:带有 finally 块
public class Demo3_11_4 {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}
}
2.4:了解具体的字节码指令含义
可以把指令复制到网页上进行查询,https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5
3:语法糖
所谓的语法糖,指java编译器把.java源代码编译为.class字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器我们的一个额外福利.
这里不再一一展示,大概列举一下:
1:默认构造器:编译器加上的,调用父类object的无参构造方法,前提是自己没有自定义构造器
2:自动拆装箱:编译器给自动转化
3:泛型集合取值:编译泛型会执行泛型擦除的动作,泛型信息消失,被当做object来处理.所以,在取值时,编译器会进行类型转 泛型擦除在字节码是看到的,但是在本地类型方法表还是可以看到的
4:可变参数: String ... args 编译后是 string[] arges ,如果不传参数,会生成空数组,而不是 null
5:foreach循环:数组foreach会被编译成for循环,集合foreach会被编译成iterator遍历
6:swith字符串:变量不能为null,字符串比较的是hashcode码和equals()方法,先比较hashcode是为了提高效率,减少可能的比较,equals()是为了防止hash碰撞. 同时执行了两遍swith,第一次是根据字符串的hashcode和equals()将字符串转换为相应的byte类型,第二遍才是利用byte类型进行比较.
7:swith枚举类:变量不能为null,会定义一个合成类,对jvm可见,对我们不可见.用来映射枚举的 ordinal 和数组元素的关系,枚举的ordinal 表示枚举对象的序号,从0开始.生成一个整形数组,存储枚举对象的序号.真正去swith的时候,实际上是对数组元素进行匹配.
8:枚举类:编译后也是一个类,将枚举元素放在一个数组中.
9:try-with-resources:简化资源关闭.其中,资源对象需要实现AutoCloseable接口,例如,InputStream,OutputStream,Connection,Statement, ResultSet等接口,使用try-with-resources可以不用写finally语句块,编译器会帮助生成关闭资源代码. 内外层异常信息都不会丢失,异常捕捉方式值得我们学习.
10:方法重写时的桥接方法: 方法重写对返回值分两种情况:1是父子类的返回值完全一致 2是子类返回值是父类返回值的子类. 编译器会新建一个方法,合成方法,对程序员不可见.
11:匿名内部类:生成一个新类,会有构造器,传值要求final修饰局部变量.
|