目录
虚拟机类加载机制
概念
1、类加载机制:描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
2、类型的加载、连接和初始化过程都是在程序运行期间完成的
类加载生命周期(类被加载到虚拟机内存到卸载出内存):加载-验证-准备-解析-初始化-使用-卸载
有且仅有五种必须立即对类初始化,即对类进行主动引用:
1、new关键字实例化对象,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外),调用一个类的静态方法。
2、java.lang.reflect对类进行反射调用
3、初始化类时发现其父类没初始化,先初始化父类(接口不符合这一条,需要真正使用父接口时才会初始化父类)
4、JVM会先初始化主类(main()方法的类)
5、MethodHandle解析方法句柄,方法句柄对应的类没有进行初始化...
类加载过程:
加载
1、类全限定名获取二进制字节流
2、字节流静态存储结构转化为方法区运行时数据结构
3、内存中生成java.lang.Class对象(HotSpot存放在方法区中),作为方法区这个类各种数据的访问入口
注意:数组本身不通过类加载器创建,它是由java虚拟机直接创建的。
验证
确保Class文件字节流符合JVM要求。
1、文件格式验证:保证输入的字节流能正确解析并存储于方法区内
2、元数据验证:保证不存在不符合java语言规范的元数据信息
3、字节码验证
5、符号引用验证
准备
正式为类静态变量分配内存并设置初始值(零值),这些变量所使用的内存都将在方法区中进行分配。
实例变量将会在对象实例化时岁对象一起分配到java堆中。
public static int value = 123 准备阶段后 value的值时0;
public final static int value = 123 准备阶段后 value的值时123;
解析
符号引用替换为直接引用的过程
1、符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
各种虚拟机实现的内存布局可以各不相同,但是他们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在JVM规范的Class文件格式中。
2、直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。
如果有了直接引用,那引用的目标必定已经在内存中存在。
类或接口解析、字段解析、类方法解析、接口方法解析
初始化:执行类构造器<client>()方法的过程
<client>()方法是有编译器自动收集类中所有类变量的赋值动作和静态语句块(static{})中语句合并产生的,编译器收集的顺序是有语句在源文件中出现的顺序决定的。
初始化
初始化阶段是执行类构造器<clinit>方法的过程
是类加载的最后一步,真正执行类中定义的java程序代码(或者说字节码)。
<clinit>:
自动收集类中所有类变量赋值动作和static{}中语句合并产生的。父类的<clinit>方法先执行。
接口的<clinit>方法不需要先执行父接口的,只有当父接口中定义的变量使用时,父接口才会被初始化
类加载器
类加载阶段中通过类全限定名来获取描述此类的二进制字节流,这个动作放在JVM外部去实现。
注意:类的唯一性由类和它的类加载器共同决定
双亲委派模型
定义:类加载器之间的层次关系
启动类加载器: 负责将存放在<Java_HOME>\lib目录中的,以.jar结尾的的类库加载到JVM内存中。
扩展类加载器:负责加载<Java_HOME>\lib\ext目录中的所有类库,开发者可以直接使用这个类加载器。
应用程序类加载器(系统类加载器):负责加载用户类路径(classpash)上所指定的类库,开发者可以直接使用这个类加载器,程序没有自定义类加载器,默认就是用的这个类加载器
自定义类加载器
双亲委派模型的工作过程:收到类加载请求,自己不会加载这个类,而是把这个请求委派给父类加载器去完成,只有父类加载器无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,自定义加载器才会尝试自己去加载。
虚拟机字节码执行引擎
栈帧
栈帧:用于支持虚拟机进行方法调用和方法执行的数据结构。(局部变量表、操作栈、动态链接、返回地址)
在活动线程中只有位于栈顶的栈帧才是有效的,称为当前栈帧。
与这个栈帧相关连得方法叫当前方法。在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了。
局部变量表
局部变量表:变量值存储空间,用于存放方法参数和方法内部定义的局部变量。局部变量不存在准备阶段,所以不会有默认赋值。reference代表对象实例引用。
容量以变量槽(slot)为最小单位,每个slot存放一个boolean、byte、chat...等类型的数据。虚拟机通过索引定位局部变量表,非static方法0索引位置存放的是当前对象的实例的引用(this),slot是可以重用的。
操作数栈
操作数栈:先入后出,最大深度在编译的时候已确定。当方法刚开始运行的时候,方法的操作数栈是空的,在方法执行过程中,字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。不同的操作数栈也可能会有一部分公用的内存,无需进行额外参数赋值传递。
例如:进行算术运算的时候是通过操作数栈来进行的,调用其它方法用操作数栈进行参数传递。
动态链接
动态链接:栈帧包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
字节码中的方法调用指令就以常量池中指向方法的符号引用作为参数。
静态解析:符号引用会在类加载阶段或者第一次使用的时候转化为直接引用。
动态链接:在每一次运行期间转化为直接引用。
方法返回地址
方法返回时可能需要在栈帧中保存一些信息,用来恢复它的上层方法的执行状态。
程序源码、词法分析、单词流、语法分析、抽象语法树、解释器、优化器、生成器、目标代码
方法调用
一切方法调用在Class文件里存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(直接引用)。
非虚方法:静态方法、私有方法、实例构造器<init>、父类方法、final修饰的方法;在类加载时就会把符号引用解析为该方法的直接引用。
高效并发
1、处理器、高速缓存、缓存一致性协议、主内存。主内存共享的多cpu回写数据到主内存时,以哪个为准?需要定义缓存一致性协议。jvm及时编译器有指令重排序的优化。