类文件结构
- JVM 提供的语言无关性
-
Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8个字节进行存储。
-
Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:“无符号数”和“表”。
-
无符号数属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、 2 个字节、 4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串值。
表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上也可以视作是一张表
-
每个 Class 文件的头4个字节被称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件
-
紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号(Minor Version),第7和第8个字节是主版本号(Major Version)。Java的版本号是从45开始的,JDK 1.1 之后 的每个 JDK 大版本发布主版本号向上加1(JDK 1.0~1.1 使用了 45.0~45.3 的版本号),高版本的 JDK 能 向下兼容以前版本的 Class文件,但不能运行以后版本的 Class 文件,因为《Java虚拟机规范》在 Class 文件校验部分明确要求了即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的 Class 文件。
类加载的过程
-
代码编译的结果:从本地机器码转变为字节码
-
Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型,这个过程被称作虚拟机的类加载机制。字节码加载到 JVM 内存中。
-
javac 把 java 代码转换成 .class 字节码文件,再由 jvm 通过 ClassLoader 及其子类加载 class 文件,翻译成二进制指令,Java 字节码的执行由 jvm 解释器引擎完成
- 加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,类型的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性(也称为动态绑定或晚期绑定)。
加载
-
将 .class 文件或者 zip 包通过二进制字节流读入 JVM
-
1)通过 ClassLoader 在 classpath 中获取 XXX.class 文件,将其以二进制流的形式读入内存。
2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
-
验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
-
文件格式验证:验证字节流是否符合Class文件格式的规范,并且能否被当前版本的虚拟机处理
-
元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求
-
字节码验证:通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的
-
符号引用验证:是对类自身以外(常量池中的各种符号引用)的各类信息进行匹配性校验,通俗来说就是,该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。
准备
-
正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段
-
这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区本身是一个逻辑上的区域
-
这时候进行内存分配的仅包括类变量,而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中
解析
-
Java 虚拟机将常量池内的符号引用替换为直接引用的过程
-
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
-
直接引用:直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄
-
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行
-
类或接口的解析、字段解析、方法解析、接口方法解析
初始化
- 在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控制。直到初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序
类加载器
-
通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)
-
启动类加载器(Bootstrap ClassLoader):使用C++语言实现,负责加载虚拟机启动所需要的类库。它只能加载自己能够识别的类。
-
扩展类加载器(Extension Class Loader):负责加载<JAVA_HOME>\lib\ext目录中,或者被 java.ext.dirs 系统变量所指定的路径中所有的类库
-
应用程序类加载器(Application Class Loader):(系统类加载器),负责加载用户类路径 (ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器
-
自定义类加载器(User ClassLoader):自己定义的类加载器,继承自抽象类java.lang.ClassLoader
参考
- 《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》——周志明
- https://blog.csdn.net/randompeople/article/details/103964350