字节码文件
每一个java类被编译后都会生成一个对应的.class字节码文件,通常以16进制来查看,数据是以二进制流的形式存储的,中间没有分隔符。
.class文件是编译的结果,只加载了常量、静态成员变量、静态成员方法、static代码块等。在程序运行时才会加载进JVM内存。
.class字节码文件由十个部分组成:
MagicNumber(魔数) :
位于.class字节码文件最开头的四个字节,是固定值0xCAFEBABE。虚拟机加载.class文件时会先检查魔数是不是0xCAFEBABE,不是的话就拒绝加载这个文件
Version(JDK版本)
版本号,虚拟机加载前会先看看版本号是否在虚拟机支持范围之内,高版本的jvm可以加载低版本的.class文件,反之不行。具体来说就是,高版本的JRE可以加载低版本的JDK,反之不行。
Constant_pool(常量池)
- 首先的两个字节是Constant_pool_count:描述常量池中一共有多少个常量,接下来的二进制信息描述这些常量的具体信息。
- .class常量池中存放的是字面常量和符号引用。字面量是指由字母,数字等构成的字符串或者数值,它只能作为右值出现,所谓右值是指等号右边的值。字面常量和字面量有区别。符号引用 :符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,符号引用和虚拟机的布局无关。在编译的时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,多以就用符号引用来代替,而在解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。
- 字面常量主要包括String类型和被声明为final类型的常量
- 符号引用包含:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符的符号引用
- Java语言在编译的时候,这些符号引用所指向的内存中的数据还没有加载(但是static和final修饰的成员已经加载),java的动态就体现在:在运行时才会去加载这些符号引用,所以需要在加载类的时候先把这些符号引用保存在class文件中。
Access_flag(访问权限)
This_cass:保存当前类全局限定名在常量池中的索引
Super_class:保存当前类父类全局限定名在常量池中的索引
Interface:保存实现的接口列表
Fileds表:(字段表)
主要包括字段的:访问权限、修饰符(static、final、volatile等)、变量类型、变量名(这些都是指向类中的常量池的字面常量和符号引用的)。注意,此时非static的字段的值或者指向的地址还没加载,在运行时(加载进JVM后)才会加载。
Methods表:(方法表)
主要包括方法的:访问权限、返回值类型、方法名等(这些都是指向类中的常量池的字面常量和符号引用的)。注意,此时非static的方法的内容还没加载,在运行时(加载进JVM后)才会加载。
Attributes表:属性表
属性表中最重要的的就是code属性, java类中的方法被javac编译器编译成class文件后,方法体被编译成字节码指令存储在属性表的code属性中。
code属性中包括操作数栈的深度、局部变量的大小、还有方法体对应的字节码指令。
但是在字节码中,字节码指令只有只有指令,还没有操作数。在运行时,字节码指令会将数据从java栈的局部变量表和操作数栈中来回移动,完成方法体中的代码。
类加载机制
- 前面讲了class文件的细节,下面介绍jvm是如何将这些class文件加载进虚拟机内存的(也就是类加载的过程)。
类的生命周期:
类的加载过程:
- 加载
- 连接(验证、准备、解析)
- 初始化
类加载的时机:
- 遇到new指令时
- 通过反射调用时
- 当初始化一个类时,会先初始化其父类
- 调用类的静态方法、静态成员变量
1、加载
- 根据类的全限定名获取类的字节码文件
- 将字节码文件中的静态结构 转化成方法区中的运行时的动态数据结构
- 创建这个类的Class对象(注意这个对象是在方法区中存储的)
2、验证
- 验证字节码文件最开头的魔数
- 验证字节码文件的第二个部分:JDK的版本号,是否和JVM版本号对应
3、准备
- 为类中static类型的成员变量分配内存和赋初始0值,注意是在方法区中分配的。
4、解析
- 解析的过程就是将字节码中的常量池中的符号引用转为直接引用。
- 符号引用就是用一个符号来描述引用的目标,不一定指向目标的内存地址;
- 直接引用就是直接指向内存地址了
5、初始化
- 初始化是调用类的构造器方法,这才是真正执行类中的代码
- 为类中的静态成员变量赋值
- 执行类中静态方法和静态代码块中的字节码指令
类加载器
类加载器把类的class文件加载进jvm内存
1、根类加载器
- 加载<JAVA_HOME>/lib目录下的类到jvm内存
2、扩展类加载器
- 加载<JAVA_HOME>/lib/ext目录下的类到jvm内存
3、应用类加载器
- 加载用户自定义的类
双亲委派机制
这些类加载器有上面这种父子的层级关系,一个类加载器收到加载类的请求时, 会先找它的父类能不能加载这个类,能的话就加载,不能的话就继续向上传递。如果所有的父加载器都不能加载,那么就自己来完成加载,如果自己也完成不了加载,那么就交给子加载器加载。
为什么要有双亲委派机制