类加载过程
类加载的时机
一个类型被加载到虚拟机内存中开始,到卸载出内存为止、它的整个生命周期将会经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。其中验证、准备、解析为连接。
类被主动加载的 7 种情况
- 创建类的实例
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如 Class.forName("com.test.Test")
- 初始化一个类的子类
- Java虚拟机启动时被标记为启动类的类, 就是包含 main 方法的类(Java Test)
- JDK1.7开始提供的动态语言支持,java.lang.invoke.MethodHandle实例的解析结果REF_getStatic, REF_putStatic, REF_invokeStatic句柄对应的类没有被初始化则初始化。
其它加载情况
- 当 Java 虚拟机初始化一个类时,要求它所有的父类都被初始化,单这一条规则并不适用于接口。
- 在初始化一个类时,并不会先初始化它所实现的接口
- 在初始化一个接口时,并不会先初始化它的父类接口
- 因此,一个父接口并不会因为他的子接口或者实现了类的初始化而初始化,只有当程序首次被使用特定接口的静态变量时,才会导致该接口的初始化。
- 只有当前程序访问的静态变量或静态方法确实在当前类或当前接口定义时,才可认为是对接口或类的主动使用。
- 调用 ClassLoader 类的 loadClass 方法加载一类,并不是对类的主动使用,不会导致类的初始化。
测试例子 1:
public class Test_2 extends Test_2_A { static { System.out.println("子类静态代码块"); } { System.out.println("子类代码块"); } public Test_2() { System.out.println("子类构造方法"); } public static void main(String[] args) { new Test_2(); } } class Test_2_A { static { System.out.println("父类静态代码块"); } { System.out.println("父类代码块"); } public Test_2_A() { System.out.println("父类构造方法"); } public static void find() { System.out.println("静态方法"); } } //代码块和构造方法执行顺序 //1).父类静态代码块 //2).子类静态代码块 //3).父类代码块 //4).父类构造方法 //5).子类代码块 //6).子类构造方法
测试例子 2:
public class Test_1 { public static void main(String[] args) { System.out.println(Test_1_B.str); } } class Test_1_A { public static String str = "A str"; static { System.out.println("A Static Block"); } } class Test_1_B extends Test_1_A { static { System.out.println("B Static Block"); } } //输出结果 //A Static Block //A str
加载
在硬盘上查找并且通过 IO 读入字节码文件,使用到该类的时候才会被加载,例如调用 main 方法, new 关键字调用对象等,在加载阶段会在内存中生成这个类的 java.lang.Class
对象, 作为方法区这个类的各种数据的访问入口。
验证
校验字节码文件的正确性
准备
给类的静态变量分配内存,并且赋予默认值
解析
将符号引用替换为直接引用,该节点会把一些静态方法(符号引用,比如 main() 方法)替换为指向数据所存内存的指针或句柄等(直接引用),这就是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。
初始化
对类的静态变量初始化为指定的值,执行静态代码块。
类加载器
- 引导类加载器(Bootstrap Class Loader) 负责加载
<JAVA_HOME>\lib\
目录或者被-Dbootclaspath
参数指定的类, 比如: rt.jar, tool.jar 等 。
- 拓展类加载器(Extension Class Loader) 负责加载
<JAVA_HOME>\lib\ext\
或-Djava.ext.dirs
选项所指定目录下的类和 jar包。
- 应用程序类加载器(System Class Loader) 负责加载
CLASSPATH
或-Djava.class.path
所指定的目录下的类和 jar 包。
- 自定义类加载器:负责加载用户自定义包路径下的类包,通过 ClassLoader 的子类实现 Class 的加载。
测试文件:
public class TestJVMClassLoader { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(DESKeyFactory.class.getClassLoader()); System.out.println(TestJVMClassLoader.class.getClassLoader()); System.out.println(); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassLoader = appClassLoader.getParent(); ClassLoader bootstrapClassLoader = extClassLoader.getParent(); System.out.println("bootstrapClassLoader: " + bootstrapClassLoader); System.out.println("extClassLoader: " + extClassLoader); System.out.println("appClassLoader: " + appClassLoader); System.out.println(); System.out.println("bootstrapLoader 加载以下文件:"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (URL url : urls) { System.out.println(url); } System.out.println(); System.out.println("extClassLoader 加载以下文件:"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoader 加载以下文件:"); System.out.println(System.getProperty("java.class.path")); } }