1.Java代码编译过程
- 准备过程:初始化插入式注解处理器
- 解析与填充符号表过程
- 词法、语法分析,将字符流转为标记集合,构造抽象语法树
- 填充符号表,产生符号地址和符号信息
- 插入式注解处理器的注解处理
- 分析与字节码生成过程
- 标注检查,静态信息检查
- 数据流与控制流分析,动态检查
- 解语法糖
- 字节码生成
2.JVM组成和运行
2.1.组成
- 类加载器:加载字节码,即class文件,只负责加载不管是否可以运行
- 运行时数据区:存放数据的,Java内存方面的问题都集中在这里
- 执行引擎:将class文件中的指令解释给操作系统,将JVM指令集翻译为操作系统指令集
- 本地库接口:负责调用本地接口,调用不同语言的接口给Java用,如Java驱动,地图制作引擎等。这种方式主键被Socket通信、WebService等方式取代。
2.2.运行
- JVM的装入环境和配置,使用java.exe找JRE
- 自己目录下的JRE
- 父目录下的JRE
- 注册中注册的JRE
- 装载JVM,通过LoadJavaJVM装入JVM文件,LoadLibrary装载JVM动态连接库等。
- 初始化JVM,获得本地调用接口。
- 运行Java程序,jar包的话先从Manifest中获得Main-Class的值,然后main函数调用LoadClass装载该主类。Class的话直接LoadClass装载类。
3.类加载机制
3.1.类的生命周期与类加载阶段
类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。
1.加载
加载过程完成以下三件事:
- 通过类的完全限定名称获取定义该类的二进制字节流。
- 将该字节流表示的静态存储结构转换为方法区的运行时存储结构。
- 在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。
其中二进制字节流可以从以下方式中获取:
- 从 ZIP 包读取,成为 JAR、EAR、WAR 格式的基础。
- 从网络中获取,最典型的应用是 Applet。
- 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
- 由其他文件生成,例如由 JSP 文件生成对应的 Class 类。
2.验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括文件格式、元数据验证、字节码验证和符号引用验证。
3.准备
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值(设置为0等),如果是final的话就直接设置为真实值,使用的是方法区的内存。
实例变量不会在这阶段分配内存,它会在对象实例化时随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
4.解析
将常量池的符号引用替换为直接引用的过程。
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
5.初始化
初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 <clinit>() 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
<clinit>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。
接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。
3.2.对象实例化过程
- <init>()方法可以重载多个,类有几个构造器就有几个<init>方法
- <init>()方法中的代码执行顺序为:父类变量初始化->父类代码块->父类构造器->子类变量初始化->子类代码块->子类构造器
- 静态变量->静态代码块->普通变量->普通代码块->构造器
3.3.类初始化时机
1.主动引用
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
- 当使用 JDK 1.7 的动态语言支持时,