JVM 中篇
JVM 中篇
字节码与类的加载篇
概述
java规范和虚拟机规范的关系
JAVA 规范指的是
java语言的规范,使用符合java规范的编译器讲java编译成class。比如javac编译器。
JVM 规范指的是java虚拟机规范
有很多实现,比如阿里的实现、华为的和ibm的实现。它们负责从导入class文件开始工作。
字节码文件的跨平台性
通过java编译器编译完成的文件class,可以在不同的平台上运行,只要平台上安装了JVM。
java的前端编译器
java编译器要完成编译有以下几个步骤。
- 词法解析
- 语法解析
- 语义解析
- 生成字节码
前段编译器和后端编译器的关系
前端编译器编译后的文件有可能进入解释器被执行,也有可能成为热点代码进入JIT执行。在JIT中代码执行效率会高很多。这也是为什么java被定义为半解释半编译行语言。
其他前端编译器
- javac
- ECJ eclipse的
- AspectJ IDEA的
后端编译器
AOT
程序运行之前直接把文件翻译成机器指令。
会破坏java的跨平台性。
目前只处于linux中
JIT
在HotSpot中将热点代码编译为机器码。
java文件看不到的执行过程
例1:Integer
1 | Integer i1 = 10; |
再字节码中回去调用Integer.valueOf();
Integer的静态代码块中有堆一个定义的静态数组 cache进行赋值
从-128到127,并且再调用Integer.valueOf时间会比较是否在诉数组范围内,如果在的话就直接从cache[]取得。所以小于128的值的对比是true,应为是同一个对象。
列2: String 的hello world拼接
1 | 0 new一个String builder |
列3: 父子类
Father
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package org.akachi.classFile;
/**
* @Author akachi
* @Email zsts@hotmail.com
* @Date 2022/5/11 19:34
*/
public class Father {
public int x = 10;
public Father(){
print();
x = 20;
}
public void print(){
System.out.println("Father:"+x);
}
}Son
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package org.akachi.classFile;
/**
* @Author akachi
* @Email zsts@hotmail.com
* @Date 2022/5/11 19:34
*/
public class Son extends Father {
int x = 30;
public Son(){
this.print();
x = 40;
}
public void print() {
System.out.println("son x:"+this.x);
}
public static void main(String[] args) {
new Son();
}
}
执行结果:
1 | son x:0 |
执行 原理:
Father init执行过程
Son的init方法
成员变量的复制过程:
在调用Son的init方法时会去调用father的init方法,并且会调用father的init方法,但是调用的print方法确是子类重写的。但子类的x变了并未被复制所以这里打印出来的是子类的x的默认值0.
验证:
1 | public class Son extends Father { |
将代码改成以下使调用father时不会调用printSon就可以使值打赢正常。
变了默认初始化过程:
默认初始化、显示初始化、构造器初始化、有了对象后可以对象.属性或对象.方法的方式堆成员变量赋值。
Class文件结构
任何一个Class都对应一个Java类,但是并非所有class都是以磁盘的方式呈现的。
Class的本质是个二进制流,可以只存在于内存。
CLass文件采用类似于C语言结构体的方式来存储,这种结构中只有两种数据类型:无符号数和表。
- 无符号数、以u1 u2 u4 u8 代表字节数。无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
- 表是由一系列无符号数活其他作为数据项构成的符合数据类型,所以表都习惯性的以”_info”结尾。用于描述层次和关系的复合结构数据。整个Class文件本质上使一张表。由于表没有固定长度,所有通常会在其前面加上个数说明。
CLass组成部分:
- 魔数
- CLass文件版本
- 常量池
- 访问标志
- 类索引、父类索引、接口索引
- 字段表集合
- 放发表集合
- 属性表集合
解释:
The ClassFile
Structure
A class
file consists of a single ClassFile
structure:
1 | ClassFile { |
The items in the ClassFile
structure are as follows:
魔数
4个字节,标识编译器。识别它是一个class文件的标识。
文件版本
两个 U2 的版本表示大版本和小版本。
minor_version、major_version;
常量池
- 2个字节的常量池长度标识constant_pool_count;
- cp_info 长度是constant_pool[constant_pool_count-1]
访问标识
2个字节的access_flag
表示类的类型、是类、接口、enum、有没有final?
类索引、父类索引、接口结合索引
- 2字节的类名 this_class;
- 2字节的父类 super_class;
- 2字节的接口数量 interfaces_count;
- 2字节接口名 [interfaces_count]个;
字段信息
- 2字节 字段数量 fields_count
- field_info fields_count个u2
方法信息
- 2字节的方法数量 methods_count
- method_info 方法信息 [methods_count]个方法信息;
属性表集合
- 2个字节的属性表信息数量 attributes_count;
- 属性信息 attribute_info attributes_count个属性信息;
什么是字节码
java源代码经过前端编译后形成的二进制文件,但是这是用于JVM执行的字节码指令,并非机器码。
JVM在执行字节码时一开始性能会差一些预热之后通过JIT将热点代码缓存后性能会达到与C相同的性能。
概念:
操作码(opcode)
代表特定含义的操作码
操作数(operand)
0个或多个
虚拟机指令
虚拟机指令有一个字节长度的代表特定含义的操作码和随后的0个至多个代表操作所需参数的操作数。
查看字节码文件方式:
通过Javap
1
javap -v className.class
通过Idea JClassLib 插件
通过Binary Viewer 等工具查看。
参考资料 JAVA8虚拟机规范第四章:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
解读字节码
必要工具:
Binary Viewer
idea JClasslib:
字面量和符号引用:
标致 | 类型 |
---|---|
F | Float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | LJava/lang/object; |
[ | 数组类型,代表一维数组。比如:Str [] [] [] is [[[Ljava.lang.String; |
在加载class文件实会建立动态链接库,将字常量池中的符号引用指向动态链接库 (Current Class Constant Pool Reference).
建立动态链接库会在类的加载器子系统> 连接阶段的>解析阶段建立。
符号引用和直接引用的区别:
符号引用
相当于引用的mata,有一组符号表示如何建立应用。在class文件中存在。
直接引用
内存指针
常量表类型
字节码指令集与解析举例
访问标识符
类索引
父类索引
接口索引集合
- 计数器
- 值
会有一个标识符标识其接口的数量
字段
计数器
值
字段名是在常量池中被声明的,字段名索引会指向常量池。