④. 过程三:初始化(Initialization)
- ①. 为类变量赋予正确的初始化值
②. 初始化阶段就是执行类构造器方法< clinit >()的过程。此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码快中的语句合并而来
public class ClassInitTest { private static int num=1; //类变量的赋值动作 //静态代码快中的语句 static{ num=2; number=20; System.out.println(num); //System.out.println(number); 报错:非法的前向引用 } //Linking之prepare: number=0 -->initial:20-->10 private static int number=10; public static void main(String[] args) { System.out.println(ClassInitTest.num); System.out.println(ClassInitTest.number); } }
③. 若该类具有父类,Jvm会保证子类的< clinit >() 执行前,父类的< clinit >() 已经执行完成。clinit 不同于类的构造方法(init) (由父及子,静态先行)
public class ClinitTest1 { static class Father{ public static int A=1; static{ A=2; } } static class Son extends Father{ public static int B=A; } public static void main(String[] args) { //这个输出2,则说明父类已经全部加载完毕 System.out.println(Son.B); } }
④. Java编译器并不会为所有的类都产生<clinit>()初始化方法。哪些类在编译为字节码后,字节码文件中将不会包含<clinit>()方法?
一个类中并没有声明任何的类变量,也没有静态代码块时
一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时
一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式 (如果这个static final 不是通过方法或者构造器,则在链接阶段)
/** * @author TANGZHI * @create 2021-01-01 18:49 * 哪些场景下,java编译器就不会生成<clinit>()方法 */ public class InitializationTest1 { //场景1:对应非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法 public int num = 1; //场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法 public static int num1; //场景3:比如对于声明为static final的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法 public static final int num2 = 1; }
⑤. static与final的搭配问题
(使用static + final修饰,且显示赋值中不涉及到方法或构造器调用的基本数据类型或String类型的显式赋值,是在链接阶段的准备环节进行)
/** * @author TANGZHI * @create 2021-01-01 * * 说明:使用static + final修饰的字段的显式赋值的操作,到底是在哪个阶段进行的赋值? * 情况1:在链接阶段的准备环节赋值 * 情况2:在初始化阶段<clinit>()中赋值 * 结论: * 在链接阶段的准备环节赋值的情况: * 1. 对于基本数据类型的字段来说,如果使用static final修饰,则显式赋值(直接赋值常量,而非调用方法)通常是在链接阶段的准备环节进行 * 2. 对于String来说,如果使用字面量的方式赋值,使用static final修饰的话,则显式赋值通常是在链接阶段的准备环节进行 * * 在初始化阶段<clinit>()中赋值的情况: * 排除上述的在准备环节赋值的情况之外的情况。 * 最终结论:使用static + final修饰,且显示赋值中不涉及到方法或构造器调用的基本数据类型或String类型的显式赋值,是在链接阶段的准备环节进行。 */ public class InitializationTest2 { public static int a = 1;//在初始化阶段<clinit>()中赋值 public static final int INT_CONSTANT = 10;//在链接阶段的准备环节赋值 public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100);//在初始化阶段<clinit>()中赋值 public static Integer INTEGER_CONSTANT2 = Integer.valueOf(1000);//在初始化阶段<clinit>()中赋值 public static final String s0 = "helloworld0";//在链接阶段的准备环节赋值 public static final String s1 = new String("helloworld1");//在初始化阶段<clinit>()中赋值 public static String s2 = "helloworld2"; public static final int NUM1 = new Random().nextInt(10);//在初始化阶段<clinit>()中赋值 }
⑥. clinit()的调用会死锁吗?
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕
正是因为函数<clinit>()带锁线程安全的,因此,如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个线程阻塞,引发死锁。并且这种死锁是很难发现的,因为看起来它们并没有可用的锁信息
package com.xiaozhi; /** * @author TANGZHI * @create 2021-05-25 */ class StaticA { static { try { Thread.sleep(1000); } catch (InterruptedException e) { } try { Class.forName("com.xiaozhi.StaticB"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println("StaticA init OK"); } } class StaticB { static { try { Thread.sleep(1000); } catch (InterruptedException e) { } try { Class.forName("com.xiaozhi.StaticA"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println("StaticB init OK"); } } public class StaticDeadLockMain extends Thread { private char flag; public StaticDeadLockMain(char flag) { this.flag = flag; this.setName("Thread" + flag); } @Override public void run() { try { Class.forName("com.xiaozhi.Static" + flag); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(getName() + " over"); } public static void main(String[] args) throws InterruptedException { StaticDeadLockMain loadA = new StaticDeadLockMain('A'); loadA.start(); StaticDeadLockMain loadB = new StaticDeadLockMain('B'); loadB.start(); } }