一个Java类的运行流程

一个Java类从编写到运行大致要经历这么几个过程:编译(前端编译生成class文件)、加载、验证、准备、解析、初始化、使用、卸载,本篇文章不讲解卸载相关内容。
上述几个过程中,加载、验证、准备、初始化和卸载这几个过程必须按部就班的开始,但是完成顺序可以无序。此外解析过程在JDK为了支持运行时绑定的特性时,可以出现在初始化之后。
我们来理解一下这几个过程。
一个Java类的运行流程_第1张图片

编译

一个Java类的运行流程_第2张图片
编译后生成的class文件有一定的结构,特定字段有特定的含义,可供后续过程使用。

加载

加载要完成的一个核心内容就是将指定的文件对应的二进制流(class文件)加载到虚拟机中。J《Java虚拟机规范》规定具体包括以下三个步骤:
1.通过一个全限定名来获取定义此类的二进制流
2.将这个字节流代表的静态存储结构转化为方法区的运行时数据结构
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
Class对象可以看成是对元数据信息的抽象。
上述规定中注意事项:
1.注意上述过程中没有指定类的二进制流必须来源于class文件,正式因为这个规定才让许多技术不受限制,比如:
二进制流从zip压缩包中获取,典型的如Jar包、War包。
从网络中获取,典型应用Web Applet。
运行时生成,典型应用动态代理。
由其它文件生成,比如JSP。
从加密文件中获取,用于安全性要求比较高的场景,防止class文件被篡改。
2.加载过程可以由虚拟机内置的类加载器来完成,也可以有用户自定义的类加载器来完成。
3.数组类的加载本身不经过类加载器来完成,而是有Java虚拟机直接在内存中动态构造出来,但数组中的元素类型最终还要靠类加载器来完成加载。
4.方法区的数据存储格式并未强制要求,由具体虚拟机的实现来完成
5.方法区的运行时结构将会生成一个Class对象,这个对象放在Java堆中,作为程序访问方法区中的类型数据的外部接口。

验证

大多数class文件可以由Java源码编译而来,因此一些不符合规定的动作将会被拒绝编译。但是上面也了解到,class文件来源很多,而且完全可以字节手写,因此在进行加载时不得不对其进行验证,以保证程序的安全性。验证阶段大致分为四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证。具体不细说。

准备

准备阶段主要完成的工作是将类中定义的静态变量分配内存并设置初始值(0 NULL等),此外注意静态变量是存储在方法区(即JDK7以前是真实存在的方法区中,JDK7开始就在堆中)。
需要注意的是:
1.此过程并不包括实例变量
2.

//此时分配初始值
public static int value = 123//因为在编译完成后类字段的字段属性表中就将其标记为ConstantValue,那么在准备阶段就会将其初始化为123
public static final int value = 123

解析

解析完成的主要内容是将符号引用转换为直接引用,可以理解为将value = 123 转换为 地址(0x123456) = 123。

初始化

初始化阶段是执行类构造器方法的过程,是javac编译器生成的,该方法包括所有类变量的负值和静态语句块的执行,顺序是按照源文件中出现的先后顺序产生的。静态语句块只能访问到定义在它前面的变量,定义在它后面的只能赋值不能访问。同时父类的方法要早于子类的执行。此外该方法不是必须要有的,如果没有静态变量、静态语句块则可以没有该方法。此方法确保只执行一次,即是在多线程的情况下也不发生变化。
总结,有上面我们可以得到一个重要结论,变量、代码块和构造方法的执行流程:类变量(静态代码块)赋初始值 ---->类变量(静态代码块)赋真值 clinit方法---->实例变量(普通代码块)赋默认值---->实例变量(普通代码块)赋真值 init方法,此外常量如final static修饰的则在准备阶段就直接赋真值了。

你可能感兴趣的:(Java,java,jvm,开发语言)