文章目录
一、概述
常用字节码指令、源代码到字节码过程中的优化处理;字节码文件要运行,那么要经过一个类加载器
二、类文件结构
根据以下信息一个个比对即可知道字节码文件中包含的信息。
三、字节码指令
1. 字节码的解析
Oracle提供了javap工具反编译class文件。
javap -v HelloWorld.class
一些常用指令的说明
getstatic 用来加载静态变量
ldc 加载参数
invokevirtual 预备调用成员方法
2. 常量池载入运行时常量池
源码:
package cn.itcast.jvm.t3.bytecode; /*** 演示 字节码指令 和 操作数栈、常量池的关系 */
public class Demo3_1 {
public static void main(String[] args) {
int a = 10; //short类型的数字跟字节码是存在一起的
int b = Short.MAX_VALUE + 1; //超过了short的话 存在了运行时常量池
int c = a + b;
System.out.println(c);
}
}
这里的运行时常量池是方法区的组成部分。找其中一些方法引用、成员变量引用、具体数值的时候,可以在运行时常量池中去找。
将class文件常量池中的信息载入到运行时常量池
3. 方法字节码载入方法区
4. main线程开始运行,分配栈帧内存
stack=2,locals=4
最大操作数栈的深度,局部变量表的长度->
决定了栈帧内存的大小
main开始运行,栈帧分配好。操作数栈是4个字节
5. 执行引擎开始执行字节码
执行引擎读取main方法中的字节码指令,开始运行。
四、构造方法对应的字节码指令
-
<cinit>()V
编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法<cinit>()V
<cinit>()V
方法会在类加载的初始化阶段被调用 -
<init>()V
编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后
五、方法调用对应的字节码指令
ivokespecial和ivokestatic都属于静态绑定,也就是在字节码指令生成的时候就知道如何找到哪个类的哪个方法了。 public可能存在方法重写的情况,编译期间无法确定自己是调用哪个对象的方法。所以invokevirtual(普通成员方法)是动态绑定,在运行的时候才知道。
补充:
new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈
dup 是复制操作数栈栈顶的内容,本例即为【对象引用】,为什么需要两份引用呢,一个是要配合 invokespecial 调用该对象的构造方法
"<init>":()V
(会消耗掉栈顶一个引用),另一个要配合 astore_1 赋值给局部变量
一共有两份入栈,第一个用来配合构造方法;另一个赋值给局部变量
最终方法(final),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静态绑定
普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态
成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】
比较有意思的是 d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了😂
还有一个执行 invokespecial 的情况是通过 super 调用父类方法
六、语法糖
定义 : java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成
和转换的一些代码,主要是为了减轻程序员的负担。
- 默认构造器
public class Candy1 {
}
有一个默认的无参构造器
- jdk5开始——自动拆装箱
- jdk5后——泛型擦除
- 可变参数
public static void foo(String... args)
- for-each循环
- switch 从 JDK 7 开始,switch 可以作用于字符串和枚举类
switch用于字符串: 第一遍是根据字符串的 hashCode 和 equals 将字符串的转换为相应byte 类型,第二遍才是利用 byte 执行进行比较。
为什么第一遍时必须既比较 hashCode,又利用 equals 比较呢?hashCode 是为了提高效率,减少可
能的比较;而 equals 是为了防止 hashCode 冲突
…