类的加载过程(类的生命周期)详解 (3)

在这个阶段并不会像初始化阶段中那样会有初始化或者代码被执行。

public class LinkedTest { private static int num; private static final int num1 = 1; private static final String name = "wyz"; private static final String name1 = new String("wyz"); }

类的加载过程(类的生命周期)详解

17.3.3. 环节3:链接阶段之Resolution(解析)

在准备阶段完成后,就进入了解析阶段。解析阶段(Resolution),将类、接口、字段和方法的符号引用转为直接引用。

具体描述

符号引用就是一些字面量的引用,和虚拟机的内部数据结构和和内存布局无关。比较容易理解的就是在Class类文件中,通过常量池进行了大量的符号引用。但是在程序实际运行时,只有符号引用是不够的,比如当如下println()方法被调用时,系统需要明确知道该方法的位置。

17.4. 过程三:Initialization(初始化)阶段

主要进行类的初始化,调用<clinit>方法

17.4.1. static与final的搭配问题

说明:使用static+ final修饰的字段的显式赋值的操作,到底是在哪个阶段进行的赋值?

情况1:在链接阶段的准备环节赋值

情况2:在初始化阶段<clinit>()中赋值

结论: 在链接阶段的准备环节赋值的情况:

对于基本数据类型的字段来说,如果使用static final修饰,则显式赋值(直接赋值常量,而非调用方法通常是在链接阶段的准备环节进行

对于String来说,如果使用字面量的方式赋值,使用static final修饰的话,则显式赋值通常是在链接阶段的准备环节进行

在初始化阶段<clinit>()中赋值的情况: 排除上述的在准备环节赋值的情况之外的情况。

最终结论:使用static+final修饰,且显示赋值中不涉及到方法或构造器调用的基本数据类到或String类型的显式财值,是在链接阶段的准备环节进行。

private static int num = 1;//初始化阶段 private static final int NUM1 = 10;//准备阶段 private static final String name = "wyz";//准备阶段 private static final String name1 = new String("hello");//初始化阶段 private static final int NUM2 = new Random().nextInt(10);//初始化阶段 17.4.2. <clinit>()的线程安全性

对于<clinit>()方法的调用,也就是类的初始化,虚拟机会在内部确保其多线程环境中的安全性。

虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

正是因为<clinit>函数是带锁线程安全的,因此,如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个线程阻塞,引发死锁。并且这种死锁是很难发现的,因为看起来它们并没有可用的锁信息。

如果之前的线程成功加载了类,则等在队列中的线程就没有机会再执行<clinit>()方法了。那么,当需要使用这个类时,虚拟机会直接返回给它已经准备好的信息。

17.4.3. 类的初始化情况:主动使用vs被动使用

Java程序对类的使用分为两种:主动使用和被动使用。

主动使用

Class只有在必须要首次使用的时候才会被装载,Java虚拟机不会无条件地装载Class类型。Java虚拟机规定,一个类或接口在初次使用前,必须要进行初始化。这里指的“使用”,是指主动使用,主动使用只有下列几种情况:(即:如果出现如下的情况,则会对类进行初始化操作。而初始化操作之前的加载、验证、准备已经完成。

实例化:当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。

静态方法:当调用类的静态方法时,即当使用了字节码invokestatic指令。

静态字段:当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者putstatic指令。(对应访问变量、赋值变量操作)

反射:当使用java.lang.reflect包中的方法反射类的方法时。比如:Class.forName("com.atguigu.java.Test")

继承:当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。在初始化一个类时,并不会先初始化它所实现的接口,在初始化一个接口时,并不会先初始化它的父接口,因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态字段时,才会导致该接口的初始化。

default方法:如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。

main方法:当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwfyzp.html