参考资料:
《深入理解Java虚拟机——JVM高级特性与最佳实践(第2版)》
对jvm的学习笔记
一。基本流程
-
执行java代码首先需要将它编译而成的class文件加载到java虚拟机中,加载后java类会被存放于方法区中,实际运行时,虚拟机会执行方法区内的代码。
-
每当调用进入一个java方法,java虚拟机会在当前线程的java方法栈中生成一个栈帧,用来存放局部变量以及字节码的操作数,这个栈帧大小是提前计算好的,而且java虚拟机不要求栈帧在内存空间里连续分布。当退出当前执行的方法时,不管是正常还是异常返回,java虚拟机都会弹出当前线程的当前栈帧,并将之舍弃。 、
从硬件来看java字节码无法直接执行,所以java虚拟机需要将字节码翻译成机器码,在hotspot中有两种方式,
- 解析执行,逐条将字节码翻译成机器码并执行。无需等待编译。
- 及时编译JIT 将一个方法中包含的所有字节码编译成机器码后再执行。运行速度快
二。虚拟机如何加载java类
从class文件到内存中的类,要经过三个大步骤 加载,链接,初始化
java语言类型有两大类,有8个基本类型(long,double,int,boolean,byte,short,float,chart),已经由java虚拟机预先定义好的。和三个引用类型(类,接口,数组),数组是java虚拟机直接生成,其他两种是有对应的字节码。
无论是何种形式都会被加载到虚拟机中成为类或者接口,无论是直接生成的数组类,还是加载的类,java虚拟机都要对应进行连接和初始化。
加载
概念:查找字节流,并且并且据此创建类的过程。
java里面有个所有加载器的父-加载类(java.lang.ClassLoader) 所有其他类的加载器都是它的子类(这些加载器也是要有父类加载器先加载到java虚拟机中,才能执行类加载功能)。使用双亲委派
模型,每当一个类加载器收到加载请求,它会先将请求转发到父类加载器。在父类加载器没有找到所请求的类的情况下,该类加载器才会尝试去加载。
链接(验证,准备,解析)
概念:将创建成的类合并至java虚拟机中,使之能够执行的过程。它分为验证,准备,解析(非必须) 三个阶段.
- 验证阶段: 在于确保被加载类能够满足java虚拟机的约束条件。通常而言,java编译器生成的类文件必然满足java虚拟机约束条件。有四个阶段的检验动作。
- 文件格式验证
- 是否魔数开头。
- 版本号是否在虚拟机处理范围内。
- 常量池中是否有不被支持的常量类型。
- …
- 元数据验证
- 是否有父类。
- 类的父类是否集成了不允许被继承的类。
- 类中字段,方法是否与父类产生矛盾(覆盖父类的final,或出现不符合规则的方法重载)。
- …
- 字节码验证
- 保证方法体中的类型转换有效。
- 保证指令不会跳转到方法体以外的字节码指令。
- 加载本地变量表时候类型不一致。
- …
- 符号引用验证
- 符号引用中通过字符串描述的全限定名是否能找到对应类。
- 在指定类中是否存在符合方法的字段描述符。
- 符号引用中的类,字段,方法的访问性。
验证阶段很重要但不一定是必要的,如果所有的运行的全部代码都已经反诬使用和验证过,在实施阶段可以考虑使用-Xverify:none来关闭大部分验证措施。缩短虚拟机类加载时间
- 文件格式验证
- 准备阶段: 为被加载类的静态变量分配内存。java代码中对静态字段的具体初始化(通常仅仅是赋初始值:零值,特使情况是final修饰的静态变量则赋实际值),会在稍后的初始化阶段进行(实例变量是在对象实例化随着对象一起分配到java堆中)。除了分配内存,虚拟机还会在此阶段构造其他跟类层次相关的数据结构,比如用来实现虚拟方法的动态绑定方法表
- 解析阶段: class文件被加载到java虚拟机前,这个类无法知道其他类和方法,字段所对应的具体地址,甚至不知道自己方法,因此,当需要应用这些成员的时候,java编译器会生成一个符号引用,在运行阶段,这个符号引用一般能够无歧义的定位到具体目标上,对于一个方法调用,编译器会生成一个包含目标所在类的名字,目标方法的名字,接受参数类型以及返回值类型的符号引用,来指代所要调用方法。 解析阶段的目的,正是将常量池里这些符号引用解析成为实际(直接)引用的过程,如果符号引用指向一个未被加载的类,或者未被加载类的字段或者方法,那么解析将触发这个类的加载。但未必触发这个类的连接以及初始化。 Java 虚拟机规范并没有要求在链接过程中完成解析。它仅规定了:如果某些字节码使用了符号引用,那么在执行这些字节码之前,需要完成对这些符号引用的解析。
初始化
概念:初始化是为标记了常量的字段赋值,以及执行<clinit>方法的过程,<clinit>方法中编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,java虚拟机会通过加锁,同步来确保类的
方法仅被执行一次。
类的初始化何时会被触发呢?JVM 规范枚举了下述多种触发情况:
- 当虚拟机启动时,初始化用户指定的主类;
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
- 子类的初始化会触发父类的初始化;
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 使用反射 API 对某个类进行反射调用时,初始化这个类;
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。