🌈hello,你好鸭,我是Ethan,西安电子科技大学大三在读,很高兴你能来阅读。
✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!
🔥个人主页:Ethan Yankang
🔥推荐:史上最强八股文||一分钟看完我的几百篇博客
🔥温馨提示:划到文末发现专栏彩蛋 点击这里直接传送
🔥本篇概览:详细讲解了《剑指JVM》——第19章5——链接4解析——类装载子系统5🌈⭕🔥
【计算机领域一切迷惑的源头都是基本概念的模糊,算法除外】
🌈章节引出:
前一篇章:
🌈章节速览:
初始化阶段
1.概述
类的初始化是类装载的最后一个阶段。如果前面的步骤都没有问题,那么表示类可以顺利装载到系统中,然后JVM才会开始执行Java字节码,也就是说到了初始化阶段,JVM才真正开始执行类中定义的 Java 程序代码。
初始化阶段的重要工作是执行类的<clinit>()方法(即类初始化方法),该方法仅能由Java编译器生成并被JVM调用,程序开发者无法自定义一个同名的方法,也无法直接在Java程序中调用该方法。<clinit>()方法是由类静态成员的赋值语句以及 static 语句块合并产生的。通常在加载一个类之前,JVM 总是会试图加载该类的父类,因此父类的<clinit>()方法总是在子类<clinit>()方法之前被调用【双亲委派模型原型】,也就是说,父类的 static 语句块优先级高于子类,简要概括为由父及子,静态先行。
Java编译器并不会为所有的类都产生<clinit>()方法。以下情况 class 文件中将不会包含<clinit>()方法。
【紧扣定义就好——<clinit>()方法是由类静态成员的赋值语句以及 static 语句块合并产生的。】
(1)一个类中并没有声明任何的类变量,也没有静态代码块时。
(2)一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时。
(3)一个类中包含 static fnal修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式。
代码清单 19-4展示了哪些情况不会产生<clinit>()方法。
查看该类对应的方法信息,如图19-7所示,可以看到不存在<clinit>()方法
2. static final搭配的赋值时机
在第19.3.2节中讲解了static与fnal 定义的变量在准备阶段完成赋值,但是并不是所有的变量都在链接阶段的准备阶段完成赋值,下面通过代码案例说明不同情况下的不同阶段赋值如代码清单 19-5所示。
package com.itheima;
public class InitializationTest1 {
// 静态变量,初始化为1
public static int a = 1;
// 静态常量,值为10
public static final int INT_CONSTANT = 10;
// 静态常量,值为100,使用Integer的valueOf方法
public static final Integer INTEGER_CONSTANT1 = Integer.valueOf(100);
// 静态变量,值为1000,使用Integer的valueOf方法
public static Integer INTEGER_CONSTANT2 = Integer.valueOf(1000);
// 静态常量,字符串"helloworld0"
public static final String s0 = "helloworld0";
// 静态常量,使用new关键字创建的字符串"helloworld1"
public static final String s1 = new String("helloworld1");
}
对应clint<>()中的字节码指令:
0 iconst_1
1 putstatic #7 <com/itheima/InitializationTest1.a : I>
4 bipush 100
6 invokestatic #13 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
9 putstatic #19 <com/itheima/InitializationTest1.INTEGER_CONSTANT1 : Ljava/lang/Integer;>
12 sipush 1000
15 invokestatic #13 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
18 putstatic #23 <com/itheima/InitializationTest1.INTEGER_CONSTANT2 : Ljava/lang/Integer;>
21 new #26 <java/lang/String>
24 dup
25 ldc #28 <helloworld1>
27 invokespecial #30 <java/lang/String.<init> : (Ljava/lang/String;)V>
30 putstatic #33 <com/itheima/InitializationTest1.s1 : Ljava/lang/String;>
33 return
从字节码指令中看到只有定义类成员变量、INTEGER_CONSTANT1、INTEGER_CONSTANT2和s1时是在物始化阶段的方法中完成,那么另外两个类变量是怎么赋值的呢?
通过jclaslib 查看字段属性表,如图19-8所示,可以看到只有INT_CONSTANT 和helowordd0 两个常量拥ConstantValue,说明INT CONSTANT=10 和 String s0="helloworld0"是在链接阶段的难备阶段完成的。
我们得出的结论就是,基本数据类型和String类型使用static和 final 修饰,并且显式赋值中不涉及方法或构造器调用,其初始化是在链接阶段的准备环节进行,其他情况都是在初始化阶段进行赋值。
3.<clinit>()方法的线程安全性
对于<clinit>0)方法的调用,JVM 会在内部确保其多线程环境中的安全性。
JVM 会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。正是因为方法<clinit>()带锁线程安全的,如果在一个类的<clinit>()方法中有耗时很长的操作,就可能造成多个线程阻塞,导致死锁,这种死锁是很难发现的,因为并没有可用的锁信息。如果之前的线程成功加载了类则等在队列中的线程就没有机会再执行<clinit>()方法了,当需要使用这个类时,JVM会直接返回给它已经准备好的信息。
为了解决死锁问题:
1.避免在 <clinit>() 中执行耗时操作
2.延迟初始化复杂逻辑,可以有效防止此类死锁问题。
💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖
热门专栏推荐
🌈🌈计算机科学入门系列 关注走一波💕💕
🌈🌈CSAPP深入理解计算机原理 关注走一波💕💕
🌈🌈微服务项目之黑马头条 关注走一波💕💕
🌈🌈redis深度项目之黑马点评 关注走一波💕💕
🌈🌈JAVA面试八股文系列专栏 关注走一波💕💕
🌈🌈JAVA基础试题集精讲 关注走一波💕💕
🌈🌈代码随想录精讲200题 关注走一波💕💕
总栏
🌈🌈JAVA基础要夯牢 关注走一波💕💕
🌈🌈JAVA后端技术栈 关注走一波💕💕
🌈🌈JAVA面试八股文 关注走一波💕💕
🌈🌈JAVA项目(含源码深度剖析) 关注走一波💕💕
🌈🌈计算机四件套 关注走一波💕💕
🌈🌈数据结构与算法 关注走一波💕💕
🌈🌈必知必会工具集 关注走一波💕💕
🌈🌈书籍网课笔记汇总 关注走一波💕💕
📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!