0.我们自己编写的java代码是如何在不同的操作系统上运行起来的?
java文件通过javac编译成.class文件,这种中间码被称为字节码,然后由JVM加载字节码,运行时,解释器将字节码解释成一行行的机器码执行,在程序运行期间,即时编译器会将热点代码解释成机器码,在程序整个运行过程中,解释器和即时编译器相互配合,执行代码。jvm加载字节码的过程被称为类加载。
1.加载
注意点:
这里的class文件不一定是本地的class文件(不一定是程序员写的java代码生成的class文件),泛指各种二进制流,比如来自网络,或者即时生成的class文件,(比如很著名的动态代理技术,就使用的即时计算出来的class,然后实例化代理对象),可以自由选择二进制流的来源,这虽然是jvm的一个小接口,但是有时候很方便我们对程序的调节。
2.链接
2.1验证
第一个步骤对文件格式的验证其实是发生在加载过程的,验证通过才能顺利加载,顺利加载之后,此时方法区内虽然已经存在了该class的静态结构,堆中也存在了该class类行的对象,但这并不代表jvm已经认可了这个类,如果程序想要使用这个类,就必须进行连接,而链接的第一步就是进一步对这个类进行验证,来看看到底对方法区内的class静态结构进行了哪些方面的验证,
第一个是元数据的验证
第二个是字节码的验证
上面两个验证简单概括说就是对class静态结构进行语法和语义上的分析,保证他不会产生危害虚拟机的行为。如果这俩个验证通过,虚拟机会姑且认为该class是安全的,但并不意味着验证已经完全结束了,还有一道对符号引用进行验证的步骤,是在解析阶段发生的,解析阶段可以在初始化阶段之前或者之后进行,其实验证包含了很多个步骤,分散在各个不同的阶段内。
在元数据,字节码验证通过,虚拟机会姑且认为该class是安全的,就会进到准备阶段
2.2.准备
准备阶段就是为该类中定义的静态变量赋0值(仅仅是静态变量不是成员变量),
jdk8之前用永久代方式实现方法区,jdk8之后弃用永久代方式,采用元空间直接内存方式实现方法区,(方法区是抽象概念,元空间是实现方式,不能说元空间代替了方法区),(应该理解为,在8之前,类的元信息,常量池,静态变量等都存储在永久代这种具体实现中,在8及以后,常量池,静态变量被移除方法区转移到了堆中,元信息依然保留在方法区,但是存储方式改成了元空间)(要搞清楚抽象概念和具体实现之间的差别)
准备阶段完成之后进入解析阶段。
2.3.解析
这个阶段主要做的事情就是将符号引用替换为直接引用,
当一个类被编译为class之后,假如这个类称为A,并且A中引用了B,在编译阶段,A不知道B有没有被编译,而且此时,B也一定没有被加载,所以A肯定不知道B的实际地址,A怎么找到B呢,此时在A的.class文件中,将使用一个字符串S代表B的地址,S就被称为符号引用,在运行时,如果A发生了类加载,到解析阶段发现B还未被加载,那么就会触发B的类加载,把B加载到虚拟机中,此时A中B的符号引用,将会被替换为B的实际地址,这被称为直接引用,这样A就能找到B的实际地址,能够真正的调用B。
但是事情没有这么简单,java通过后期绑定的方式实现多态,后期绑定概念的实现就是这里的动态解析。接着上面的说,如果A调用的B是一个具体的实现类,就称为静态解析,因为解析的目标类很明确。而假如上层java代码使用了多态,这里的B可能是抽象类或者接口,B可能有两个具体的实现类C和D,此时,B的具体实现并不明确,当然也就不知道使用哪个具体类的直接引用用来进行替换,(既然不知道,就会等一等),直到运行过程中发生了调用,此时虚拟机栈中(调用方法的栈)中将会得到具体的类型信息,这时候再进行解析,就能用明确的直接引用来替换符号引用了,这也是为什么解析阶段有时候会发生在初始化阶段之后,这就是动态解析,用它实现了后期绑定。
当解析部分完成意味着整个链接部分的完成
3.初始化
初始化阶段会判断代码中是否存在主动的资源初始化操作,如果有,就执行,主动的资源初始化操作是指成员变量的赋值,静态变量的赋值等。
总结:
请回忆一下类加载的五个阶段
下一期内容:
流程搞懂了,那虚拟机是如何使用代码来实现这些步骤的呢?这需要涉及到java的类加载器了。