一、JVM架构图
- 最上层:javac编译器将编译好的字节码class文件,通过java 类装载器执行机制,把对象或class文件存放在 jvm划分内存区域。
- 中间层:称为Runtime Data Area,主要是在Java代码运行时用于存放数据的,从左至右为方法区(永久代、元数据区)、堆(共享,GC回收对象区域)、栈、程序计数器、寄存器、本地方法栈(私有)。
- 最下层:解释器、JIT(just in time)编译器和 GC(Garbage Collection,垃圾回收器)
二、字节码文件
查看字节码指令的工具 jclasslib bytecode viewer客户端工具
1、字节码指令
字节码指令省略。。。。
Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
例如 经典i++与++i:
- 0:压栈10放入到局部变量表
- 2:出栈将这个数 10 其放到局部变量表为 1 的位置(0通常放this)
- 3:从局部变量表取出 10 数据放入到栈中
- 4:将局部变量表的 10 数据+1
- 7:出栈重新将这个数 10 其放到局部变量表为 1 的位置覆盖了值
- 11:从局部变量表取出 10 数据再次放入到栈中然后进行打印
区别:
7:从局部变量表取出 11 数据放入到栈中(此时含操作数栈含有两个槽位 自顶向下 为 11 10)
8:将栈顶的两个元素弹栈相乘得到结果放入到栈顶 110
9:将栈顶元素弹栈放入到局部变量表的1中
- i++ 会在加法运算前先将局部变量表1位置的数据(10)压栈
运算完成后重新将操作数栈里面的栈顶数据(10)覆盖到局部变量表1的位置- ++i 在运算后才会将新的值进行压栈
public void testAbsoluteConfig() throws Exception {
int k = 10;
k = k+(k++)+(++k); // 10 10 12
System.out.println(k);
}
总结:1、k先进行运算 10 +10
2、k在自己加一 11
3、k先自己+1 为12
4、在相加 10+10+12 = 32
2、字节码文件的组成
魔数:
每个 Class 文件开头的4个字节的无符号整数称为魔数(Magic Number)
它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件。即:魔数是Class文件的标识符。
魔数值固定为0xCAFEBABE。不会改变
Class文件版本
版本号 编译字节码文件的 jdk 版本号
Java 的版本号是从45开始的,JDK 1.1之后的每个JDK大版本发布主版本号向上加1。
常量池:
public class SpringbootTest {
String s = "lslsmls";
public static void main(String[] args) {
SpringbootTest s = new SpringbootTest();
System.out.println(s.s);
}
}
-
常量池计数器
常量池计数器用来标识常量池中条目的数量,并且它总是比实际条目数大1。这是因为常量池的索引是从 1 开始的,而不是从 0 开始。这意味着如果常量池计数器的值是 n,那么常量池中实际包含的条目数就是 n - 1。0表示不引用任何一个常量池项目
-
常量池表:
它包含了类定义中所有可能用到的常量信息。常量池表由一系列常量池条目组成,每个条目都代表一个特定类型的常量。这些条目可以是直接常量(如整数、浮点数、字符串)或符号引用(如类名、字段名、方法名)。
Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态链接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用再在类创建时或运行时解析、翻译到具体的内存地址之中。
虚拟机在加载class文件时才会进行动态链接,也就是说,Class文件中不会保存各个方法和字段的最终内存布局信息,因此,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。当虚拟机运行时,需要从常量池中获得对应的符号引用,再在类加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中。这里说明下符号引用和直接引用的区别与关联:
符号引用: 符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
直找引用: 直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已存在于内存之中了。
访问标识(或标志)
类索引,父类索引,接口索引集合
用来描述这个类实现了哪些接口、继承类的索引
字段表集合
fields表中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接
口中某个字段的完整描述。
cp_info #10 就指向常量池
方法表集合
方法表集合包含了类或接口中所有方法的描述。每个方法表项描述了一个方法的详细信息。
属性表集合
用于存储类或成员(字段或方法)的附加信息。每个属性表项都描述了一个特定的属性,并且可以包含附加的数据。属性表集合可以出现在类文件的多个地方,包括类文件的顶级结构、字段表项、方法表项等。