虚拟机基础概念
- jvm:java虚拟机,一台虚拟的计算机,具有语言独立性和平台独立性,各种语言编写的程序,只要编译成java字节码,都可以运行在java虚拟机上,不用关心底层平台是window还是linux
- class文件:java字节码文件,程序编译之后生成的文件,jvm只认该文件
- 从编码到执行过程
- jvm规范:jvm由jvm规范描述,具体的实现由各个厂商(开发者)定义
- jvm、jre、jdk关系
class文件结构
由于class文件属于jvm的核心基础,涉及到很多方面,这里只是梳理class文件的关键骨架
概览
类型 | 项目 | 说明 |
---|---|---|
u4 | magic | 文件格式标志,固定为0xCAFEBABE |
u2 | minor_version | 字节码文件次版本号 |
u2 | major_version | 字节码文件主版本号 |
u2 | constant_pool_count | 常量池的大小 |
cp_info | constant_pool_count | 常量池 |
u2 | access_flags | 访问修饰符,如public、private等 |
u2 | this_class | class文件定义的类或接口,这里是对应常量池的索引 |
u2 | super_class | 父类,对应常量池的索引 |
u2 | interfaces_count | 接口数量 |
u2 | interfaces[interfaces_count] | 接口数组,此处是常量池的索引 |
u2 | fields_count | 字段数量 |
field_info | fields[fields_count] | field_info类型数组 |
u2 | methods_count | 方法数量 |
method_info | methods[methods_count] | method_info类型数组 |
u2 | attributes_count | 属性数量 |
attribute_info | attributes[attributes_count] | attribute_info类型数组 |
- u1——无符号数1个字节
- u2——无符号数2个字节
- u4——无符号数4个字节
常量信息(cp_info)
类型 | 项目 | 说明 |
---|---|---|
u1 | tag | 常量类型,截止java se 16,常量类型有17类 |
u1 | info[] | 该项的内容依据tag变化 |
字段信息(field_info)
类型 | 项目 | 说明 |
---|---|---|
u2 | access_flags | 访问修饰符 |
u2 | name_index | 方法名,常量池索引 |
u2 | descriptor_index | 方法描述,常量池索引 |
u2 | attributes_count | 属性数量 |
attribute_info | attributes[attributes_count] | 属性信息数组 |
方法信息(methods_info)
类型 | 项目 | 说明 |
---|---|---|
u2 | access_flags | 访问修饰符 |
u2 | name_index | 方法名,常量池索引 |
u2 | descriptor_index | 方法描述,常量池索引 |
u2 | attributes_count | 属性数量 |
attribute_info | attributes[attributes_count] | 属性信息数组 |
属性信息(attribute_info)
类型 | 项目 | 说明 |
---|---|---|
u2 | attribute_name_index | 属性名,常量池索引 |
u4 | attribute_length | info数组长度 |
u1 | info[attribute_length] | 属性信息数组 |
其中属性分为三大类:
- jvm虚拟机解释class文件类(6个)
- JAVA SE类库或工具解释class文件类(10个)
- class文件的元数据(metadata)(30个)
jvm指令
JVM指令(SE 16)目前分为9大类,具体每个指令的详细说明请参考JVM规范。
- Load And Store
- 算术运算指令
- 类型转换指令
- 对象创建和操作
- Operand Stack Management
- 控制转移(Control Transfer)
- 方法调用和返回
- 异常
- 同步
运行时内存结构
共享内存
- Heap——如果堆内存超出设置大小,将抛出OutOfMemoryError错误
- Method Area——逻辑上属于堆内存,具体位置由JVM实现确定,如果超出设置大小,将会抛出OutOfMemoryError错误
- Run-Time Constant Pool——如果超过设置大小,将抛出OutOfMemoryError错误
线程内存
- PC寄存器——正在执行的指令地址
- JVM Stacks大小是否可以设定或动态调整,依据JVM的具体实现,如果超出栈设置的大小,将会抛出StackOverflowError错误,如果可扩展,如无足够内存,将抛出OutOfMemoryError错误
- Frame——每个方法调用线程都会创建一个Frame,方法调用完成则销毁
- Dynamic Linking——运行时常量池的引用
- Frame——每个方法调用线程都会创建一个Frame,方法调用完成则销毁
- Native Method Stacks —— 每个线程创建一个本地方法栈,大小是否可以设定或动态调整,依据JVM的具体实现,如果超出栈设置的大小,将会抛出StackOverflowError错误,如果可扩展,如无足够的内存,将抛出OutOfMemoryError错误
总体来说,JVM运行时内存可分为静态信息和动态信息两部分,静态信息即为类(接口)的结构信息,动态信息即为类的实例数据和代码执行信息
对象的内存结构
对于对象的结构,jvm规范没有要求,这里主要介绍hotspot的对象内存结构
概览
普通对象
普通对象包括mark word,以及实例的类型信息class word和实例字段,mark word 和class word 称为对象头
数组对象
对于数组对象,除了mark word和class word外,还包括数组的长度以及数组元素
Mark Word
mark word在32位环境下位4个字节,64位环境下为8字节,32位环境下mark word不同状态下的结构信息如下:
64位环境下的mark word不同状态下的结构信息如下:
mark word主要包括三方面的信息:
- GC相关的信息
除了age记录对象经历了多少次GC外,还会把对象移动的信息编码进mark word - identity hash code
在使用系统的hashCode时,将会把hash code的值存在mark work中,对象的整个生命周期内都不会改变,同时**由于mark word中存储了hashcode,占用了在偏向锁状态下需要存储偏向的线程指针,因此,在进入同步临界区时,就不会使用偏向锁 ** - 锁信息
锁的状态迁移可以参考jdk关于Synchronization的wiki,下面的状态迁移图来自该博客
Class Word
class word主要指向实例的类信息,主要作用包括运行时类型检查、对象大小计算以及接口或虚拟方法调用
对象对齐(Object Alignment)
当对象大小不是8的倍数,jvm会采取填充的方式使对象的大小刚好为8的倍数,这样做是为了保持数据的一致,便于快速的数据存取,为了清楚的了解对象的结构,这里我们使用JOL工具,具体使用参见JOL项目主页
对齐规则
- 字段的定义顺序!=内存顺序—— 为了节约内存,类的字段排列是以字段的类型标准,而不是以定义的顺序,double/long、int/float、short/char、bool/byte、references
- 如果对象存在对齐填充,可以把合适的字