- Java语言,类型的加载、连接、初始化都是在程序运行期间完成的
- 类的生命周期:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)
- 连接(Linking):验证、准备、解析
- 加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的
- 有且仅有5种情况必须对类进行“初始化”
-
- 遇到new、getStatic、putStatic、ivokeStatic
- 对类进行反射调用
- 初始化一个类,如果其父类还没初始化
- 虚拟机启动时,执行的主类(包含main)
- 当使用1.7的动态语言支持,如果一个java.lang.invoke.MethodHandle实例的最后解析结果有REF_getStatic、REF_putStatic、REF_ivokeStatic,其所对应类也要初始化
除此之外,所有引用类的方式都不会被初始化,成为被动引用
- 被动引用的例子:
-
- 通过子类调用父类的静态字段。子类不会被初始化
- 通过数组定义来引用类,不会触发此类的初始化
- 常量在编译阶段会存入调用类的常量池中(常量传播化),本质上并没有直接引用定义常量的类,因此不会触发定义常量的类的初始化
- 类的加载过程
-
- 通过一个类的全限定名来获取定义此类的二进制字节流(jar、Applet、Proxy、JSP等)
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
- 验证:文件格式验证、元数据验证、字节码验证、符号引用验证
- 准备:是正式为类变量分配内存并设置类变量初始值的阶段
- 解析:虚拟机将常量池内的符号引用替换为直接引用的过程
- 初始化:<clinit>()方法,非必需
-
- <clinit>()是由编译器自动收集所有类变量的赋值操作和静态语句块合并产生的
- 在构造函数之前执行完毕,父类的<clinit>()先执行
- 执行接口的<clinit>()不需要先执行父接口的<clinit>()
- 只有一个线程去执行这个类的<clinit>()
- 双亲委派模型:
-
- 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,只有父加载器无法完成加载,子加载器才尝试自己去加载
- 启动类加载器 <- 扩展类加载器 <- 应用程序加载器 <- 自定义加载器
时间: 2024-11-08 20:13:48