static与final的搭配问题

初始化阶段,简言之,为类的静态变量赋予正确的初始值。

具体描述

  • 类的初始化是类装载的最后一个阶段。如果前面的步骤都没有问题,那么表示类可以顺利装载到系统中。此时,类才会开始执行Java字节码。(即:到了初始化阶段,才真正开始执行类中定义的Java程序代码。)
  • 初始化阶段的重要工作是执行类的初始化方法:<clinit>()方法。
    • 该方法仅能由Java编译器生成并由JVM调用,程序开发者无法自定义一个同名的方法,更无法直接在Java程序中调用该方法,虽然该方法也是由字节码指令所组成。
    • 它是由类静态成员的赋值语句以及static语句块合并产生的。

代码举例

public class InitializationTest {public static int id = 1;public static int number;static {number = 2;System.out.println("father static(}");}// clinit方法:// 0 iconst_1// 1 putstatic #2 <T1/InitializationTest.id>// 4 iconst_2// 5 putstatic #3 <T1/InitializationTest.number>// 8 getstatic #4 <java/lang/System.out>//11 ldc #5 <father static(}>//13 invokevirtual #6 <java/io/PrintStream.println>//16 return
}

说明

  • 在加载一个类之前,虚拟机总是会试图加载该类的父类,因此父类的<clinit>总是在子类<clinit>之前被调用。也就是说,父类的static块优先级高于子类。
  • Java编译器并不会为所有的类都产生<clinit>()初始化方法。哪些类在编译为字节码后,字节码文件中将不会包含<clinit>()方法?
    • 一个类中并没有声明任何的类变量,也没有静态代码块时
    • 一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时
    • 一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式

代码举列

/*** 哪些场景下,java编译器就不会生成<cLinit>()方法*/
public class InitializationTest1 {//场景1:对应非静态的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法public int num = 1;//场景2:静态的字段,没有显式的赋值,不会生成<clinit>()方法public static int numl;//场景3:比如对于声明为static final的基本数据类型的字段,不管是否进行了显式赋值,都不会生成<clinit>()方法public static final int num2 = 1;}

关于static + final


/*** 说明:使用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(100);     // 在初始化阶段<clinit>()中概值public static final String se = "helloworlde";     // 在链接阶段的准备环节赋值public static final String s1 = new String("helloworld1");     // 在初始化阶段<clinit>()中赋值public static final int NUM1 = new Random().nextInt(10);//在初始化阶段clinit>()中赋值
}

所有非final的static都是在初始化<clini>()显示赋值
不涉及符号引用(链接阶段的解析环节),就直接在链接阶段的准备环节显示赋值(没验证)

<clinit>()的线程安全性

  • 对于<clinit>()方法的调用,也就是类的初始化,虚拟机会在内部确保其多线程环境中的安全性。
  • 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。
  • 正是因为函数<clinit>()带锁线程安全的,因此,如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个线程阻塞,引发死锁。并且这种死锁是很难发现的,因为看起来它们并没有可用的锁信息。
  • 如果之前的线程成功加载了类,则等在队列中的线程就没有机会再执行<clinit>()方法了。那么,当需要使用这个类时,虚拟机会直接返回给它已经准备好的信息。

代码举列

package T1;public class StaticDeadLockMain extends Thread {private char flag;public StaticDeadLockMain(char flag) {this.flag = flag;this.setName("Thread" + flag);}@Overridepublic void run() {try {Class.forName("T1.Static" + flag);} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println(getName() + "over");}public static void main(String[] args) throws ClassNotFoundException, InterruptedException {StaticDeadLockMain loadA = new StaticDeadLockMain('A');loadA.start();StaticDeadLockMain loadB = new StaticDeadLockMain('B');loadB.start();}
}class StaticA {static {try {Thread.sleep(100);} catch (InterruptedException e) {}try {Class.forName("T1.StaticB");} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println("staticA init Ok");}
}class StaticB {static {try {Thread.sleep(100);} catch (InterruptedException e) {}try {Class.forName("T1.StaticA");} catch (ClassNotFoundException e) {e.printStackTrace();}System.out.println("staticB init Ok");}
}

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

引言

  • Java程序对类的使用分为两种:主动使用和被动使用。
  • 主动使用才会调用<clinit>(初始化),被动使用不会引起类的初始化
  • 被动使用不会引起类的初始化,但有可能只是加载了没进行初始化,比如调用类的final+static的字段,有加载能输出字段,但没经历初始化

主动使用

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

  1. 当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化。
  2. 当调用类的静态方法时,即当使用了字节码invokestatic指令。
  3. 当使用类、接口的静态字段时(final修饰特殊考虑),比如,使用getstatic或者putstatic指令。(对应访问变量、赋值变量操作)
  4. 当使用java.lang.reflect包中的方法反射类的方法时。比如:Class.forName(“com.atguigu.java.Test”)
  5. 当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  6. 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口要在其之前被初始化。
  7. 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  8. 当初次调用MethodHandle 实例时,初始化该 MethodHandle指向的方法所在的类。(涉及解析REF getStatic、REF_putStatic、REF invokeStatic方法句柄对应的类)

针对5,补充说明:

当Java虚拟机初始化一个类时,要求它的所有父类都已经被初始化,但是这条规则并不适用于接口。

  • 在初始化一个类时,并不会先初始化它所实现的接口
  • 在初始化一个接口时,并不会先初始化它的父接口
  • 因此,一个父接口并不会因为它的子接口或者实现类的初始化而初始化。只有当程序首次使用特定接口的静态字段时,才会导致该接口的初始化。

针对7,说明:

VM启动的时候通过引导类加载器加载一个初始类。这个类在调用public static void main(String[])方法之前被链接和初始化。这个方法的执行将依次导致所需的类的加载,链接和初始化。

代码举列-主动使用

package T1;import java.util.Random;public class ActiveUse1 {// main()方法的那个类),虚拟机会先初始化这个主类static {System.out.println("ActiveUse1 的初始化(main触发)");}public static void main(String[] args) throws ClassNotFoundException {// 当创建一个类的实例时,比如使用new关键字
//        Order order = new Order();// 当调用类的静态方法时,invokestatic
//        Order.method1();// 当使用类、接口的静态字段时(final修饰特殊考虑)
//        System.out.println(Order.num);
//        System.out.println(CompareA.num);// 当使用java.lang.reflect包中的方法反射类的方法时
//        Class.forName("T1.Order");// 当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化(没能触发实现的接口)Class.forName("T1.Order");// 初始化一个接口时,并不会先初始化它的父接口
//        System.out.println(CompareC.cnum2);// 接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化Class.forName("T1.Order");}
}class Order extends OrderFather implements CompareB, CompareD {public static int num = 10;static {System.out.println("Order类的初始化过程。。。。。。。。。。。");}public static void method1() {System.out.println("静态代码 method1");}
}class OrderFather {public static int num = 20;static {System.out.println("OrderFather类的初始化过程。。。。。。。。。。。");}
}interface CompareA {public static final Thread t = new Thread() {{System.out.println("CompareA初始化");}};public static int num = 10; // 为啥这个没能触发初始化public static final int cnum2 = new Random().nextInt(10);
}interface CompareB {public static final Thread t = new Thread() {{System.out.println("CompareB初始化");}};}interface CompareC extends CompareB {public static final Thread t = new Thread() {{System.out.println("CompareC初始化");}};public static int num = 10; // 为啥这个没能触发初始化public static final int cnum2 = new Random().nextInt(10);
}interface CompareD {public static final Thread t = new Thread() {{System.out.println("CompareD初始化");}};public default void abc() {System.out.println("default");}
}

-XX:+TraceClassLoading ,可以打印出类的加载顺序,可以用来排查 class 的冲突问题。

被动使用

除了以上的情况属于主动使用,其他的情况均属于被动使用。被动使用不会引起类的初始化。
也就是说:并不是在代码中出现的类,就一定会被加载或者初始化。如果不符合主动使用的条件,类就不会初始化。

  1. 当访问一个静态字段时,只有真正声明区个字段的类才会被初始化。

    当通过子类引用父类的静态变量,不会导致子类初始化
    
  2. 通过数组定义类引用,不会触发此类的初始化

  3. 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。

  4. 调用ClassLoader类的LoadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

代码举列-被动使用

package T1;import java.util.Random;public class PassiveUse1 {public static void main(String[] args) throws ClassNotFoundException {//  1. 当访问一个静态字段时,只有真正声明区个字段的类才会被初始化。//        当通过子类引用父类的静态变量,不会导致子类初始化
//        System.out.println(Child.num);///  2. 通过数组定义类引用,不会触发此类的初始化
//        Parent[] parents= new Parent[10];
//        System.out.println(parents.getClass());// 但new的话还是会初始化
//        parents[0] = new Parent();//  3. 引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了。System.out.println(Parent.age);System.out.println(Serival1.num);// 但引用其他类的话还是会初始化System.out.println(Serival1.cnum2);//  调用ClassLoader类的LoadClass()方法加载一个类System.out.println(ClassLoader.getSystemClassLoader().loadClass("T1.Parent"));}
}class Parent {public static int num = 10;public static final int age = 20;static {System.out.println("Person 初始化");}
}class Child extends Parent {static {System.out.println("Son 初始化");}
}interface Serival1 {public static final Thread t = new Thread() {{System.out.println("Serival1 初始化");}};public static int num = 10; // 为啥这个没能触发初始化public static final int cnum2 = new Random().nextInt(10);
}

尚硅谷2020最新版宋红康JVM教程-中篇-第3章类的加载过程(类的生命周期)详解-4-过程三:Initialization(初始化)阶段相关推荐

  1. 尚硅谷2020最新版宋红康JVM教程-中篇-第4章:再谈类的加载器-02和03-类的加载器分类

    引言 JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader). 从概念上来讲,自定义类加载器 ...

  2. 尚硅谷2020最新版宋红康JVM教程更新至中篇(java虚拟机详解,jvm从入门到精通)

    目录 类的加载第一步----loading 加载".class"文件的方式: 类的加载第二步----linking![在这里插入图片描述](https://img-blog.csd ...

  3. 尚硅谷2020最新版宋红康JVM教程-16-垃圾回收相关概念

    转载: https://www.bilibili.com/video/BV1PJ411n7xZ 参考: https://gitee.com/moxi159753/LearningNotes/tree/ ...

  4. 尚硅谷2020最新版宋红康JVM教程-10-对象实例化内存布局与访问定位

    转载: https://www.bilibili.com/video/BV1PJ411n7xZ 参考: https://gitee.com/moxi159753/LearningNotes/tree/ ...

  5. 尚硅谷 宋红康 JVM教程_02_字节码与类的加载篇

    本系列相关链接 尚硅谷 宋红康 JVM教程_01_内存与垃圾回收篇--01 (20210103-20210110) https://blog.csdn.net/wei198621/article/de ...

  6. 尚硅谷 宋红康 JVM教程_01_内存与垃圾回收篇——02

    本系列相关链接 尚硅谷 宋红康 JVM教程_01_内存与垃圾回收篇--01 (20210103-20210110) https://blog.csdn.net/wei198621/article/de ...

  7. 尚硅谷-宋红康-JVM上中下篇完整笔记-JVM上篇_内存与垃圾回收篇

    前言 一.jvm及java体系结构 1. Java及JVM简介 TIOBE语言热度排行榜 https://www.tiobe.com/tiobe-index/ 世界上没有最好的编程语言,只有最适用于具 ...

  8. 尚硅谷2020最新版周阳SpringCloud(H版alibaba)框架开发教程 学习笔记

    前言:今天看到周阳老师出了新课,十分欣喜,很喜欢周阳老师的讲课风格,内容也充实,我也算是周阳老师忠实粉丝啦. 新出的springcloud第二版很符合我现阶段的学习需求.但美中不足的是,目前只有视频资 ...

  9. 尚硅谷-宋红康-JVM上中下篇完整笔记-JVM中篇

    一.Class文件结构 1.概述 1.1 字节码文件的跨平台性 所有的JVM全部遵守Java虚拟机规范:Java SE Specifications,也就是说所有的JV环境都是一样的,这样一来字节码文 ...

最新文章

  1. 让AI训练AI,阿里和浙大的“AI训练师助手”是这样炼成的
  2. SQL 2005完全卸载,重新安装
  3. linux命令大全增删改查,crudini命令
  4. how is SAP CDS view SADL load generated
  5. leetcode 563 二叉树的坡度
  6. 天然气压缩因子计算软件_测量天然气用什么流量计?
  7. Monkey测试:日志信息分析
  8. 英文演讲稿(3-5分钟)
  9. 爬取豆瓣高分电影榜实例
  10. java在regedit找不到_Windows找不到文件regedit打不开注册表的解决办法
  11. 高职高考数学可以用计算机吗,将高职高考数学成绩从39分提到120分,3个月时间可能吗?...
  12. 如何给图片降噪?看完你就学会了
  13. 该网页无法正常运作localhost 目前无法处理此请求。
  14. 【DIY】制作一个刷卡进门装置
  15. win10看不到家庭组计算机,win10专业版没有家庭组怎么办?一招帮你解决问题
  16. 【计算机网络】计算机网络核心知识点
  17. python基础_字典_列表_元组考试
  18. 【软著】申请软件著作权流程资料及方法
  19. 用MATLAB AppDesigner开发了一款的高级日期时间计算器(Advanced Date Time Calculator)
  20. 【洛谷P4084】Barn Painting【树形DP】

热门文章

  1. [***Model mj_objectArrayWithKeyValuesArray:]: unrecognized selector sent to class 0x10ace5df0
  2. ua解析接口_VIP电影解析接口(80个)
  3. AnnaAraslanova/FBNet 程序分析
  4. C++小游戏(第五弹)
  5. oracle分区表导入数据,Oracle 分区表数据的导入与导出(2)
  6. 商务型网站建设多少钱
  7. siliconc8051f Silicon C8051F编程器使用出错解决办法
  8. 一分钟带你解读光纤收发器,秒懂(一)
  9. 印刻出版 盛可以 限量《死亡賦格》
  10. 关于理性形象的塑造——歇洛克·福尔摩斯眼中的世界