关注我,每天三分钟,带你轻松掌握一个 Java 相关知识点。
虚拟机(JVM)经常出现在我们面试中,但是工作中却很少遇到,导致很多同学没有去了解过。其实除了应付面试,作为 java 程序员,了解我们写的 java 程序为什么能运行起来也是很有必要的。
我准备在接下来的一系列文章中,整理虚拟机的相关运行机制,让同学们对虚拟机有个整体的概念。(声明一下,文章内容基于周志明的《深入理解 Java 虚拟机》,也非常推荐同学们去读这本书)
先说一个知识点,咱们写的代码,都是.java 文件,但是虚拟机只认.class 文件,那么谁做的这个部分的转换呢?
看到 JDK 中 Tools&Tool APIs 了吗,其中的 javac 干了这件事。
好了我们回到正题,虚拟机是怎么把 class 文件加载到内存中并且执行使用的呢?
一个 class 从进入内存到被提出内存,总共经历了加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)这 7 个阶段,哪这 7 个阶段都干了啥呢?
一、加载
这个阶段虚拟机主要完成了 3 件事:
1. 通过一个类的全限定名来获取定义此类的二进制字节流。
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法去这个类的各种数据的访问入口。
我们可以看到,通过加载,class 文件从目标路径转到虚拟机的方法区中,并且 clss 文件中的数据能被其他人访问了。
二、验证
验证主要是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。
验证阶段包含 4 个阶段的检验动作:
1. 文件格式检验,就是说传过来的字节流一定要符合 Class 文件的格式规范。
2. 元数据验证,这个是对解析出的字节码进行语义验证,以保证其信息符合 Java 语言规范。
3. 字节码验证,这个阶段将对类的方法体进行校验分析,保证被校验类方法在运行的时候,不会危害虚拟机。
4. 符号引用验证,这个阶段校验发生在虚拟机将符号引用转换为直接引用的时候,也就是在解析阶段发生的。
有的同学看到这可能要疑问了,你这验证的 4 个步骤,都涉及了后面的流程,难道这些流程不是顺序的吗?
其实加载、验证、准备、初始化和卸载这 5 个阶段的顺序是确定的,类的加载过程必须按照这个中顺序开始,但是开始不代表进行或者完成,就是这些阶段被调用的顺序是确定的,但是他们在什么时候结束是不一定的,这些阶段通常都是互相交叉混合进行的。宝宝起名网
这里面解析阶段的执行顺序是不确定的,这是为了支持 java 的运行时绑定(多态就是动态绑定的体现,编译时不知道变量指向的是父类还是子类,只有在运行时才去找时机类型的方法表,确定方法签名调用)。
三、准备
准备阶段的工作是给类变量分配内存并使之类变量初始值的阶段。
这里说明一个概念:
类变量:类变量是 Class 级别的变量,通常用 static 修饰,它是在类层面共享的变量。
实例变量:实例变量是跟着对象走的,每 new 一个对象,就有一套类变量。
public static int value = 123;
这句在准备阶段,就会给变量 value 赋值为 0,而不是 123。这就是准备阶段干的工作,这也是你为什么可以不给实例变量赋值初值的原因(而局部变量必须赋初值以后会解释,这也跟虚拟机有关)
四、解析
解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。
怎么理解呢?你可以把这个符号引用当做虚拟机和 class 文件约定好的黑话,不管哪个虚拟机来了,都要用一套黑话他们才能交流。而他们说了什么呢,他们说了目标对象的各种信息,这些信息在各个虚拟机里描述都不一样,你可以把虚拟机理解为各个杀手组织,他们再收到同一句黑话描述暗杀对象的时候,在组织内部对对象的描述都不一样。
直接引用就是杀手组织内部翻译的黑话了,翻译出来的内容一般都包括目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
五、初始化
类的初始化阶段是类加载过程的最后一步,该阶段才真正开始执行类中定义的 Java 程序代码(或者说是字节码)。
对于初始化阶段,虚拟机规范有且仅有 5 种情况必须立即对类进行初始化:
1. 遇到 new(使用 new 关键字实例化对象)、getstatic(获取一个类的静态字段,final 修饰符修饰的静态字段除外)、putstatic(设置一个类的静态字段,final 修饰符修饰的静态字段除外)和 invokestatic(调用一个类的静态方法)这 4 条字节码指令时,如果类还没有初始化,则必须首先对其初始化
2. 使用 java.lang.reflect 包中的方法对类进行反射调用时,如果类还没有初始化,则必须首先对其初始化
3. 当初始化一个类时,如果其父类还没有初始化,则必须首先初始化其父类
4. 当虚拟机启动时,需要指定一个主类(main 方法所在的类),虚拟机会首选初始化这个主类
5. 当使用 JDK1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
以上就是今天的知识点,各位小伙伴 get 到吗?创作不易,望各位多多点赞收藏,有什么建议可以留言告诉我,我会积极采纳!