JVM基础知识
JVM结构图
字节码文件
Java虚拟机不和包括Java在内的任何语言绑定,它只与字节码文件这种特定的二进制文件格式所关联.
Class文件结构不仅仅是JVM的执行入口,更是Java生态圈的基础和核心.
字节码文件内容是什么
字节码是一种二进制的类文件,他的内容是JVM指令,而不像C,C++经由编译器直接生成机器码(JIT后面进行了优化).
class文件编译器
分为前端编译器 就是javac(ecj,eclipse的增量编译器)编译器,他把咱们写的Java代码编译为字节码文件,还有后端的编译器就是JIT,运行时编译,能把字节码文件的执行命令再编译为native的本地执行代码,让运行效率更高(这也是Java说自己的运行效率基本等同于C语言的底气所在) 后面还有个aot提前编译
javac前端编译器的编译步骤
词法解析->语法解析->语义解析->生成字节码
哪些类型对应有class对象
- class: 类对象都算,包括各种的内部类
- interface: 接口
- []: 数组
- enum: 枚举
- annotation: 注解
- primitive type: 基本数据类型
- void
字节码指令
是由一个字节长度的,代表着某种特定操作含义的操作码(opcode),以及跟随其后的零至多个代表此操作所需参数的操作数(operand)所构成.虚拟机中的许多指令并不包含操作数,只有一个操作码
字节码趣味题目
class文件结构
结构概述
- 魔数
- 每个class文件开头的4个字节的无符号整数称为魔数
- 他的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件(魔数就是Class文件的标识符)
- 魔数值固定为0xCAFFBABE不会改变
- 之所以用魔数不用扩展名是出于安全方面的考虑(因为扩展名可以随意的更改)
- Class文件版本
- 排列在magic后的第5个和第6个字节所代表的含义就是编译的副版本号minor_version,而第7个和第8个字节就是编译的主版本号major_version
- 验证字节码文件的主版本号和次版本号同样也是格式验证的任务之一,因为如果是高版本的JDK编译的字节码文件,自然不能在低版本的JVM中运行,否则JVM会抛出java.lang.UnsupportedClassVersionError异常
- 常量池(一共是有21项,(其实大小是22,0表示不引用常量池))
- constant_pool_count (常量池计数器):constant_pool_count的值等于常量池表中的成员数加1。常量池表的索引值只有在大于0且小于constant_pool_count时才会认为是有效的,对于long和 double类型有例外情况。
- constant_pool [](常量池):constant_pool是一种表结构,以 1 ~ constant_pool_count - 1为索引。它包含class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中的每一项都具备相同的特征——第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte (标记字节、标签字节)
- 访问标识(或者说是标志)
- 常量池后面就是访问标志,用两个字节来表示,其标识了类或者接口的访问信息,比如:该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final等等。
- 类索引,父类索引,接口索引集合
- 访问标志后的两个字节就是类索引;
- 类索引后的两个字节就是父类索引;
- 父类索引后的两个字节则是接口索引计数器。
- 通过这三项,就可以确定了这个类的继承关系了。
- 字段表集合(字段表用来描述类或者接口中声明的变量。这里的字段包含了类级别变量以及实例变量,但是不包括方法内部声明的局部变量)
- fields_count (字段计数器): fields_count的值表示当前class文件fields表的成员个数。fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的类字段或者实例字段。
- fields [](字段表): fields表中的每个成员都必须是一个fields_info结构的数据项,用 于表示当前类或接口中某个字段的完整描述。fields表描述当前类或接口声明的所有字段,但不包括从父类或父接口继承的那些字段。
- 方法表集合(指向常量池索引集合,它完整描述了每个方法的签名,如果这个方法不是抽象的或者不是native的,那么字节码中会体现出来。)
- methods_count (方法计数器):methods_count的值表示当前class文件methods表的成员个数。methods 表中每个成员都是一个method_info结构。
- methods [](方法表):methods表中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述。如果某个method_info结构的access_ flags项既没有设置 ACC_NATIVE 标志也没有设置ACC_ABSTRACT标志,那么该 结构中也应包含实现这个方法所用的Java虚拟机指令。
- method_info 结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、 实例初始化方法和类或接口初始化方法。methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。
- 属性表集合(不同值的集合,它提供了额外的关于这个类的信息,包括任何带有RetentionPolicy.CLASS 或者RetentionPolicy.RUNTIME的注解)
- attributes_count (属性计数器):attributes_count的值表示当前class文件属性表的成员个数。属性表中每一项都是一个attribute_info结构。
- attributes [](属性表):属性表的每个项的值必须是attribute_info结构
class结构图标示意
访问标识表
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可以设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义,jdk1.0.2之后编译出来的类这个标志默认为真 |
ACC_INTERFACE | 0x0200 | 标志这个一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口和抽象类来说,此标志值为真,其他的为假 |
ACC_SYNTHETIC | 0x1000 | 标志这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 标志这是一个注解 |
ACC_ENUM | x4000 | 标志这是一个枚举 |
字段表访问标志
方法表访问标志
方法表结构
类的加载过程
Loading(装载)阶段
- 通过一个类的全限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
类模板对象
类模板对象就是Java类在jvm当中的一个快照,jvm将从字节码文件中解析出的常量池,类字段,类方法等信息存储到类模板中,这样jvn在运行期间便能通过类模板获取java类中的任意信息,能够对Java的类成员变量遍历,也能调用方法.
反射的机制就是基于这一基础
jdk1.8之前存储在永久代 1.8以后存储在元空间
二进制流的获取方式
- 通过文件系统读入class扩展名文件
- 读入jar,war,zip等归档数据包
- 数据库中读取类的二进制数据
- 通过http请求远程的信息
- 运行时直接生成一段class的二进制信息
数组类的加载
它比较特殊,因为数组类本身并不是由类加载器去创建的,而是由jvm在运行时根据需要而直接创建的,但是数组的元素类型仍然需要依靠类加载器去创建,过程如下:
- 如果数组的元素类型是引用类型,那么就遵循定义的加载过程递归加载和创建数组的元素类型
- jvm使用指定的元素类型和数组纬度来创建新的数组类
- 如果数组类的元素类型是引用类型,数组类的可访问性就由数组元素的可访问性决定,否则数组的可访问性将被缺省定义为public
Linking(链接)阶段
- 验证(Verify,保字节码是合法合理并且符合规范的)
- 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
- 主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
- 准备(Prepare):
- 为类的静态变量分配内存,并将其初始化为默认值.
- 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;
- 这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
Java并不支持boolean类型,对于boolean类型,内部实现是int,由于int的默认值是0,所以boolean的默认值就是false
解析(Resolve)阶段
- 将常量池内的符号引用转换为直接引用的过程。
- 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行,符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的Class文件格式中。在解析阶段,jvm根据字符串的内容找到内存区域中相应的地址,然后把符号引用替换成直接指向目标的指针、句柄、偏移量等,这些直接指向目标的指针、句柄、偏移量就被成为直接引用。
- 解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
设置栈的大小
现在默认是1Mb大小(不宜设置的过大),用**-Xss size(即 -XX:ThreadStackSize)**来设置,一般默认为512-1024k,取决于操作系统,栈的大小决定了函数调用的最大可达深度
初始化阶段
简单来说就是给类的静态变量赋予正确的初始值