深入理解Java虚拟机系列笔记
类加载过程
最近开始学习Java虚拟机,今天学习了类加载的三个过程,遂写一篇博客作为学习笔记
类加载子系统概述
类加载子系统作为JVM的一部分,负责将硬盘中的class字节码文件加载到JVM中。类加载器只负责将类加载到JVM中,不保证程序一定可以正确执行,决定程序是否可以正常运行的是执行引擎。
类被加载后存储于方法区中。方法区中的内容除了类的相关信息之外,还包括运行时常量池、字符串字面量及数字常量等。
上图的几点说明
1、class存储于硬盘中
2、字节码文件加载到JVM中,存放在方法区,被称为DNA元数据模板
3、在“.class文件->JVM->DNA元数据模板”这一过程中,类加载器起到了运输工具的作用
类的加载过程
类的加载共分为三部分
第一部分:Loading(加载)
1、通过类的全名获取该类的二进制字节流
类的全名是指带包名的类名,如java.lang.String , java.util.Date等
2、将字节流所代表的静态存储结构转化为方法区的运行时数据结构
3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口
第二部分:Linking(链接)
链接部分共分成3步:验证、准备、解析
验证:确保class文件中的信息的正确性,防止危害到Java虚拟机。因为字节码文件本质上还是二进制方式存储的,用户也可以从键盘上任意捏造字节码文件,显然这样的字节码文件是不合法的,甚至会恶意攻击、篡改其他文件甚至破坏虚拟机,因此验证这一步不可或缺。能被Java虚拟机识别的class文件都以"CAFEBABE"开头
准备:为类变量分配内存并设置该类变量初始值
关于准备过程的几点注意事项:
①这里不包含用final修饰的static,因为final在编译阶段就分配好内存了,准备阶段会显示初始化
②这里不会为实例变量分配初始化,因为实例变量是在new对象时分配到堆区的,而类变量位于方法区
③总之,准备阶段只会为没有final修饰的static变量分配内存并初始化
④分配初始值是变量类型对应的初始值,如int默认值为0.boolean默认值为false等
解析:将常量池内的符号引用转换为直接引用
第三部分:初始化
初始化阶段执行类构造器方法(),这个方法不需要我们自己定义,只要这个类中存在静态变量、静态代码块,这个方法编译器会自动生成。
()方法中的指令执行顺序按照源文件中出现的顺序执行
要特别注意这里可能会出现的前向引用错误,比如下面的代码在IDEA中编译时会自动报错:
虽然类变量a在链接阶段的准备过程就已经分配好了内存,但
()方法中的代码顺序为:先执行static代码块,再执行为类变量a赋值1,因此会导致前向引用错误
public static void main(String[] args){static{System.out.println(a); //错误,前向引用报错}private static int a = 1;}
另外还要特别注意:()方法不同于类的构造器。每一个类都会有构造器,但不一定会有()方法,比如下面的类A没有()方法而类B就有
class A{public static void main(String[] args){}}class B{ public static int a = 1; public static void main(String[] args){ }}
同时,如果该类具有父类,Java虚拟机会保证父类的()方法执行完后再执行子类的()方法