一、类的生命周期
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
有且仅有以下情况,JVM必须立即对类进行初始化:
- new、getstatic、putstatic、invokestatic
- 使用反射对类进行调用时
- 初始化一个类,发现其父类还未进行初始化,则先初始化父类
(接口初始化,并不要求其父接口初始化,只有在真正使用到父接口(如引用接口中定义的常量)才会初始化;
调用类中的常量,不会触发初始化) - JVM启动时,会先初始化main方法所在类
注意以下几点,不会触发类的初始化
- 通过数组引用类,不会触发初始化,如:
SomeClass[] someClassArr = new SomeClass[10];
- 常量在编译阶段会存入调用类的常量池,本质上并没有直接引用到定义类,如:
System.out.println(Constants.HELLO);
二、类的加载过程
- 加载
- 通过类的全限定名,获取类的二进制字节流
- 将字节流代表的静态存储结构转化为方法区的运行时数据结构
- 在方法区(针对HotSpot而言)生成一个代表此类的java.lang.Class对象。
这个对象将作为程序访问方法区中这些类型数据的外部接口
- 验证
其中,符号引用验证,发生在JVM将符号引用转化为直接引用的时候(解析阶段)。 - 准备
为类变量(被static修饰的变量)分配内存并设置初始值,这些变量所使用的内存,在方法区分配
特殊情况:如果是常量,则在准备阶段直接赋给定值 - 解析
JVM将常量池内的符号引用替换为直接引用的过程,包括以下常量:
- CONSTANT_Class_info
- CONSTANT_Methodref_info
- CONSTANT_Fieldref_info
- CONSTANT_InterfaceMethodref_info
- 初始化
执行<cinit>的过程,c指class的意思。
JVM会保证<cinit>只执行一次,即便在多线程环境中
<cinit> 是编译器自动收集类中所有static变量的赋值动作和static{}, 并按源文件中出现顺序形成的
三、类加载器
每一个类加载器,都拥有一个独立的类命名空间,【命名空间 + 类全限定名】唯一确定一个类。
双亲委派模型
父加载器反馈无法加载时,才由子加载器自己加载
- 避免重复加载
- 防止JVM类(如Object.class被覆盖)
- Bootstrap ClassLoader
<JAVA_HOME>/lib下,并且仅按照文件名识别(如rt.jar) - 扩展类加载器
<JAVA_HOME>/lib/ext下 - 应用程序类加载器
classpath指定路径
网友评论