过程如下图:
加载、验证、准备、初始化、卸载这5步顺序是一定的。而初始化解析阶段可能在初始化之后。因为java支持运行时绑定
一、加载阶段
jvm会做下面的事情:
- 通过类的全限定名取到类的二进制数据流
- 把字节流中的静态存储结构转换为方法区(元空间)中运行时的数据结构
- 生成一个java.lang.Class对象,作为入口供程序访问
顺便说明一下,加载class文件的几种方式:
- 从本地系统中加载class文件
- 通过网络加载class文件
- 从zip,jar等归档文件中加载class文件
- 从数据库中提取class文件
- 从内存中加载class文件(java源文件动态编译)
二、连接阶段
(1)验证:确保加载类的正确性
- 文件格式验证:验证class文件格式是否规范。版本是否在本虚拟机处理范围之内
- 元数据验证:验证class文件语义是否规范。例如:是否有父类、是否实现了父类的抽象方法、是否重写的父类final方法、是否继承了被final修饰的类等。
- 字节码验证:验证class文件数据流和控制流是否规范。例如:操作数栈的数据类型和指令代码能配合工作、保证方法中的类型转化有效等。
- 符号引用验证:确保下面解析步骤能正确执行。例如:通过符号引用能找到对应的类和方法、符号引用的类、属性、方法权限能否被当前类访问等。
(2)准备:为类的静态变量分配内存并赋默认值
静态变量是在方法区进行分配的。默认值有0、0L、null、false等。
但对final static的静态字面值常量直接赋初值,就不赋默认值了。(字面值常量是什么?比如1234是一个int的字面值常量, “abc”是一个字符串的字面值常量等等)
(3)解析:将常量池中的符号引用替换为直接引用(内存地址)
符号引用:就是一组符号来描述目标,可以是任何字面量。例如:类或接口的全限定名、字段的名称和描述分、方法的名称和描述符
直接引用:就是指向目标的指针。
例如:一个类的静态变量。此静态变量是一个对象。解析之后,该静态变量将是一个指针,指向该对象在方法区的内存地址。
三、初始化阶段:为类的静态变量赋初值
包括静态变量的赋值和静态代码块的执行。
- 定义静态变量是指定初始值:如 public static String a="123";
- 在静态代码块里为静态变量赋值:如static{a="123";}
四、卸载阶段:jvm退出时执行
- 程序执行了System.exit()方法
- 程序正常结束
- 程序在执行中遇到错误或操作系统错误而导致jvm异常终止
附:
一、类加载的方式(class文件被jvm加载是隐式还是显式)
(1)隐式加载
- 创建类对象或使用类的静态属性
- 创建子类对象或使用子类的静态属性
- 在jvm启动时,BootStrapLoader(根类加载器)会加载jvm自身所使用的class(
%JAVA_HOME%/jre/lib
) - 在jvm启动时,ExtClassLoader(扩展类加载器)会加载
ext路径下的class
(%JAVA_HOME%/jre/lib/ext
)或java.ext.dirs
系统变量指定的路径 - 在jvm启动时,AppClassLoader(应用类加载器)会加载classpath路径下的class(CLASSPTH、-classpath、-cp、Manifest)
(2)显式加载
- ClassLoader.loadClass(className):只加载和连接、不进行初始化。
- Class.forName(String name,boolean initialize,ClassLoad loader): 使用loader进行加载和连接,根据initialize参数决定是否初始化
二、类加载的时机(class文件在什么情况下会被jvm加载)
- 使用new实例化对象时
- 反射调用类时
- 初始化类时,如果父类没有进行初始化,则先初始化父类。
- 启动程序的main方法所在的类
- 跟静态变量有关的操作时。例如:读取设置类的静态变量、静态非字面值常量,调用静态方法等
注意以下操作不会初始化:
- 通过子类引用父类的静态字段,只会初始化父类,不初始化子类。
- 定义对象数据不会初始化该类。
- 引用类的final静态字面值常量不会导致该类初始化。(非字面值常量除外)
- 获取类的class对象不会初始化,如:Person.class;
- 通过Class.forName加载类,参数initialize为false,则不会初始化该类
- 通过ClassLoader的loadClass方法加载类,不会初始化。