JVM全文
class文件概述
- java语言,跨平台的语言,
write once, run anywhere
- java虚拟机,跨语言的平台,只与
class文件
这种特点的二进制文件格式所关联,无论使用何种语言,只要能将源文件编译为正确的class文件,就可以在java虚拟机上执行
前端编译器
,负责将符合java语法规范
的java代码
转换成符合jvm规范
的字节码文件
- javac,默认提供的前端编译器
- 词法解析、语法解析、语义解析、生成字节码
- 全量编译,将所有内容,都进行重新编译
- 任何一个class文件都对应着唯一的类或接口的信息,但是class文件,不一定以磁盘文件的形式存在,class文件是一组以字节(8位)为单位的二进制流
字节顺序和数量,都被严格限定,哪个字节代表什么含义,长度多少,先后顺序,都不能改变
- clas文件采用类似C语言结构体方式进行数据存储,只有两种数据类型,
无符号数和表
- 无符号数属于基本数据类型,描述数字、索引引用、数量值或者按照UTF-8编码构成字符串,以u1、u2、u4、u8代表1、2、4、8个字节的无符号数
- 表,由多个无符号数或者其他表作为数据构成的复合数据,以
_info
结尾,用于描述有层次的复合结构的数据,没有固定长度,会在其前面加上个数说明- class文件本质就是一张表
- 字节码
- 源代码经过编译生成一个或多个字节码文件,一个类生成一个
- 字节码指令,由
一个字节长度的、代表某种特定操作含义的操作码
以及跟随其后的零至多个代表此操作所需参数的操作数
构成
结构
- 官网结构
ClassFile {
u4 magic; //魔数,文件识别
u2 minor_version; //小版本
u2 major_version; //主版本
u2 constant_pool_count; //常量池长度 10实际上是9 首索引没有分配
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags; //访问标识,类/接口+修饰
u2 this_class; //类索引名字
u2 super_class; //父类索引名字
u2 interfaces_count; //接口索引长度,一个类可以实现多个接口
u2 interfaces[interfaces_count]; //索引接口数组
u2 fields_count; //字段数
field_info fields[fields_count]; //字段表
u2 methods_count; //方法数
method_info methods[methods_count]; //方法表
u2 attributes_count; //属性数,类似变量表/异常表里的信息
attribute_info attributes[attributes_count]; //属性表
}
复制代码
- 魔数,前四位,每个class文件开头的四个字节都是
CA FE BA BE
,确定这个文件是否为一个能被虚拟机接受的有效合法的class文件 - 版本号,56位小版本,七八位大版本,
00 00 00 34
->52.0
JDK8,向下兼容 - 访问标识,两个字节,用于识别一些类或者接口层次的访问信息,最终结果是
下面的几项的或结果(并集)
- ACC_SUPER类默认都有
- 没有ACC_INTERFACE 则为类;如果有,同时也要ACC_ABSTRACT
- 如果有ACC_ANNOTATION,也要有ACC_INTERFACE
- 注解,flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
public class TestDemo4
对应字节码中00 21
- 索引,包含类索引,父类索引,接口索引
指向常量池的符号引用
常量池
- 常量池计数器+常量池表,内容丰富,class文件的资源仓库,保存字段和方法相关信息
- 常量池计数器,计数从1开始,实际长度少1,第0项空出来了,满足后面某些指向常量池的索引值的数据在特定情况下,不引用任何一个常量池项目
- 常量池表,存放编译期间生成的
字面量和符号引用
,经过类加载后,进入方法区的运行时常量池;红框jkd7引入,体现对动态语言的支持;存放的是标识tag
,表示存放哪种类型- 3456 用final修饰后的变量;byte/short/char/boolean,都以int型来保存,4个字节,32位
- 具体格式看官网
相关概念
- 虚拟机在class文件时才会进行动态链接,class文件中
不会保存各个方法和字段的最终内存布局信息
,这些字段和方法的符号引用不经过转换无法直接被虚拟机使用;当虚拟机运行时,需要从常量池中获得对应的符号引用
,再在类加载过程中的解析阶段
,将其替换成直接引用,翻译到具体的内存地址中
- 符号引用,用符号来描述所引用的目标,可以是任何形式的字面量,无歧义地定位到目标
- 直接引用,直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄,与虚拟机实现的内存布局下你管
- 字面量
- 文本字符串
- 声明为final的常量值
- 符号引用
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 全限定名
全类名
com.java.Demo
-> 全限定名com/java/Demo;
分号结束
- 简单名称,方法或者字段名称
- 描述符,描述字段的数据类型,方法的参数列表(数量、类型及顺序)和返回值
//[Ljava.lang.Object;@12a3a380
Object[] objects = new Object[10];
//[[Ljava.lang.String;@29453f44
String[][] strings = new String[10][10];
//[[D@5cad8086
double[][] doubles = new double[10][10];
复制代码
字段表集合
- 描述接口或类中声明的变量,包括
类级变量和实例变量
- 字段的名字和数据类型,都引用常量池中的符号引用,不能确定;
- 指向常量池索引集合,描述每个字段的完整信息,包括字段修饰符、访问修饰符、类变量
static
/实例变量、常量final
- 不会包含父类或者实现接口中继承而来的字段,但是会出现原本类中不存在的字段,如内部类保证对外部的访问,会保存外部类指针
- java中字段不能重载,不能重名;但是对于字节码来说,描述符不一样,就是合法的
- 字段计数器,表示当前表中,字段个数
- 字段表,字段表中每一个都是表结构,保存字段的完整信息
每一个字段的结构
访问标识
- 字段名索引,访问常量池,找到字段名
- 字段描述符索引,字段数据类型
- 属性表集合,属性计数器+表集合,初始值、注释信息等,如用
final
修饰的变量,存成为常量
- 属性表结构,属性名/属性长度(常量属性恒为2)/常量值的索引(常量池中)
方法表集合
- 指向常量池索引集合,描述每个方法的签名,包括方法修饰符、返回值类型及参数信息
- 会体现是否是抽象或者native
- 不包括父类或接口中继承的方法,会自动产生方法,如类初始化
<clinit>
和实例初始化<init>
- 方法重载,简单名称一样,但是返回值不包括在
特征签名
之中,不能靠修改返回值,进行重载
特征签名,一个方法中
各个参数
在常量池的字段符号引用的集合
但是,在class字节码中,只要描述符不一样,就是合法的,可以共存
- 方法表计数器,两字节
- 方法表结构,方法的完整描述
访问标识,修饰
- 方法名索引,指向常量池
- 方法描述符索引
- 属性表集合,具体看下面,当前
<init>
方法包含一个属性code
,但是这个code属性下又包含两个属性,行号表和局部变量表
属性表集合
- 方法表集合之后的属性集合,指class文件所属的信息,该class文件的源文件名称,注解,用于Java虚拟机的验证和运行,以及调试
- 字段表、方法表中的属性表,用于描述某些专有信息
- 属性表通用格式
- sourcefile属性表结构
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length; //始终是2
u2 sourcefile_index; //原码文件名称索引
}
复制代码
方法中属性表
- code
- 字节码对应关系,每一条字节码指令都对应一个字节
- LineNumberTable 行号对应表
LineNumberTable_attribute {
u2 attribute_name_index; //属性名
u4 attribute_length; //长度
u2 line_number_table_length; //行号表长度
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
复制代码
- local_variableTable 局部变量表
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length; //作用域长度
u2 name_index; //变量名索引
u2 descriptor_index; //描述符索引
u2 index; //槽位起始序号
} local_variable_table[local_variable_table_length];
}
复制代码
终极解析
- 原码
package com.java;
public class TestDemo4 {
public int num = 1;
public int add() {
num = num + 2;
return num;
}
}
复制代码
案例
基础案例
- 源码
public class TestDemo4 {
public int num = 1;
public int add() {
num = num + 2;
return num;
}
}
复制代码
- 字节码反编译后用idea打开,多出了默认构造方法、调用用变量归属和全类名
public class TestDemo4 {
public int num = 1;
public TestDemo4() {
}
public int add() {
this.num += 2;
return this.num;
}
}
复制代码
Integer案例
- 源码
Integer x = 5;
int y = 5;
System.out.println(x == y); //true
//是在`IntegerCache`数组现取的
Integer i1 = 10;
Integer i2 = 10;
System.out.println(i1 == i2); //true
//是新new的对象
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); //false
复制代码
- Integer底层,如果是在
low(-128)
和high(127)
之间,直接返回IntegerCache
数组中的值,否则创建新的对象
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
复制代码
- 字节码
//Integer x = 5;
0 iconst_5 //将5加载进操作数栈
//调用Integer对象的valueOf方法
1 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
4 astore_1 //存储引用类型
//int y = 5;
5 iconst_5
6 istore_2 //存储int类型
7 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>
//x == y
10 aload_1 //取出引用类型
//调用Integer对象的intValue方法,进行拆箱
11 invokevirtual #4 <java/lang/Integer.intValue : ()I>
14 iload_2
15 if_icmpne 22 (+7) //如果相等跳到22行
复制代码
多态案例
public class TestDemo3 {
public static void main(String[] args) {
Father father = new Son();
System.out.println(father.x);
}
}
class Father {
int x = 10;
public Father() {
this.print();
this.x = 20;
}
public void print() {
System.out.println("father" + x);
}
}
class Son extends Father {
int x = 30;
public Son() {
this.print();
this.x = 40;
}
@Override
public void print() {
System.out.println("Son" + x);
}
}
复制代码
- 输出
- 第一个为什么为0,在对son进行初始化的时候,先初始化父类,并调用父类中的print()方法,但是son重写了print(),就会调用son的print方法,但是由于此时son类中的x初始化为零值,那么就输出为0
- 父类初始化整体都在子类初始化前,而在调用print方法时,直接调用的是子类重写的方法,而此时子类的x仅仅是分配空间,有了零值
Son0
Son30
20 //成员变量没有重写
复制代码
近期评论