第一次创建类的实例(第一次使用 new
关键字)。
第一次访问类的静态变量或者为静态变量赋值(但 final 常量除外,它们在编译期就已经被赋值了)。
第一次调用类的静态方法。
第一次使用反射 API 对类进行反射调用。
第一次初始化一个类的子类(会首先初始化父类)。
注意:
(1)若代码中没有显式调用某个Java类加载器去加载该类,则在触发该类初始化时,jvm底层会自动找到该类适应的类加载器,进行相应类加载与类初始化
(2)类加载过程一般都只会在第一次引用该类的时候触发,若已经加载过该类到虚拟机内存里面,即使满足上面条件操作也不会再次执行类加载
(3)类初始化是类加载的最后阶段部分,却也是两个独立的执行过程。即,可以实现只加载类到内存但不初始化类,但要初始化类必须先加载类到内存里面
(4)如果该类有父类,则类加载器会先去查找其父类,且依次类推直到最顶级的类,然后再顺序往下执行每个类的类加载具体过程
(5)一个类加载过程是包括加载、验证、准备、解析、初始化这五个小阶段
(6)即,只有等父类的类加载5个阶段执行完,才会执行子类的类加载5个阶段
当 JVM 首次遇到需要使用某个类的时候,它会通过类加载器(ClassLoader)找到该类的 .class
文件。类加载器负责从指定的位置(如文件系统、网络、jar 包等)读取字节码数据到内存里面。
注意:
(1)此阶段会将类中的静态方法、非静态方法都会被加载到内存方法区中进行存储,方便后续调用,什么时候调用则什么时候触发方法操作
加载完字节码后,JVM 会对字节码进行验证,确保其符合 Java 虚拟机规范,没有安全方面的问题。验证包括语法校验、元数据验证、字节码验证和符号引用验证等步骤。
在这个阶段,JVM 会为类的静态变量分配内存,并将其初始化为默认值,这个阶段又称隐式初始化。例如,对于整型静态变量,默认值为0;对于对象引用类型的静态变量,默认值为 null
。这里不包含final修饰的静态变量,因为final修饰的静态变量是在编译期分配。
注意:
(1)此阶段只会对静态变量进行内存空间分配以及赋默认值;非静态变量则是在调用构造方法,触发对象实例化时,进行内存空间分配与赋默认值
解析阶段主要是将常量池中的符号引用转换为直接引用。符号引用是对类、接口、字段和方法的抽象表示,而直接引用是内存中实际的地址或偏移量。
这个阶段又被称为显示初始化,如果该类还没有被初始化,那么 JVM 会在适当的时候触发类的初始化(参考上文触发类初始化操作)。类的初始化主要包括以下步骤:
<clinit>
方法(由编译器自动生成,用于初始化静态变量和执行静态初始化块的代码)。注意:
(1)连续多次触发同一个类的初始化操作,jvm底层只会在第一次类加载进行类初始化,后面就不会再进行类初始化了
(2)静态变量、静态代码块,会按照它们在类中的声明顺序依次执行
(3)此阶段也只会对静态方法、静态变量、静态代码块进行操作;非静态的各种属性显式初始化不在此阶段,而是在后续调用类构造方法,对象实例化阶段进行显式初始化
(4)虽然静态变量在准备阶段已经进行隐式初始化,但是由于jvm机制原因,静态代码块中只能引用定义在其之前的静态变量;
定义在其之后的静态变量,该静态代码块中只可以对该静态变量进行赋值,却不能进行非赋值操作,否则就会报 "java: 非法前向引用" 异常。
(5)这个机制有助于提高代码的可读性和可维护性,因为在阅读代码时,开发人员可以信任变量在使用之前已经被正确地初始化。
这有助于减少代码中的错误,并使代码更容易理解和调试。
成员变量按照它们在类中的声明顺序,进行内存空间分配以及自动赋予变量类型对应的默认值
成员变量、普通代码块,会按照它们在类中的声明顺序依次执行
注意:
(1)虽然成员变量在前面已经进行隐式初始化,但是由于jvm机制原因,普通代码块中只能引用定义在其之前的变量;
定义在其之后的成员变量,该普通代码块中只可以对该成员变量进行赋值,却不能进行非赋值操作,否则就会报 "java: 非法前向引用" 异常。
(2)这个机制有助于提高代码的可读性和可维护性,因为在阅读代码时,开发人员可以信任变量在使用之前已经被正确地初始化。
这有助于减少代码中的错误,并使代码更容易理解和调试。
执行实际构造方法的代码逻辑
注意:
(1)虽然在源代码逻辑中是直接new一个对象,看似直接调用了某个类的构造方法,但是从调用这个构造方法开始,jvm底层会判断该类是否初次加载,
否就会先触发类加载以及类初始化(有父类先加载且初始化父类),接着再执行对象实例化(有父类先实例化父类),最后才会执行真正的构造方法代码逻辑。
(2)类初始化与类对象实例化是两个完全独立的过程,可以分开触发,但是类初始化必须在类对象实例化之前进行
(3)当然也可以在类初始化的过程中,手动触发对象实例化,这样是可以的但是不符合规范,不建议这么做
1. 父类非静态成员隐式初始化
2. 父类非静态成员显示初始化
3. 父类构造方法
1. 子类非静态成员隐式初始化
5. 子类非静态成员显示初始化
6. 子类构造方法
/**
* @Description TODO
* <p>
* Copyright @ 2022 Shanghai Mise Co. Ltd.
* All right reserved.
* <p>
* @Author LiuMingFu
* @Date 2024/1/3 09:47
*/
public class Father {
public static void main(String[] args) {
System.out.println("父类静态main方法");
Father father = new Father();
System.out.println("weight=" + father.getWeight());
System.out.println("===================================再次触发对象实例化过程===================================");
Father father2 = new Father();
System.out.println("===================================再次触发对象实例化过程===================================");
Father father3 = new Father();
}
public Father() {
System.out.println("父类构造方法!");
}
String job;
{
System.out.println("父类普通代码块-1,job=" + job + ",name=" + name + ",age=" + age + ",sex=" + sex + ",height=" + height);
job = getJob();
}
static int age;
static String name;
static {
// Father father = new Father();
System.out.println("父类静态代码块-1,age=" + age + ",name=" + name);
age = 20;
name = "张三";
}
Double weight = 99.0d;
{
System.out.println("父类普通代码块-2,weight=" + weight + ",job=" + job + ",name=" + name + ",age=" + age + ",sex=" + sex + ",height=" + height);
weight = getWeight();
}
static String sex = getSex();
static {
System.out.println("父类静态代码块-2,sex=" + sex + "age=" + age + ",name=" + name);
}
static Double height = getHeight();
static {
System.out.println("父类静态代码块-3,height=" + height + "sex=" + sex + "age=" + age + ",name=" + name);
}
public static String getSex() {
System.out.println("父类静态方法-getSex");
return "男";
}
public static Double getHeight() {
System.out.println("父类静态方法-geHeight");
return 170.0d;
}
public String getJob() {
System.out.println("父类普通方法-getJob");
return "Java工程师";
}
public Double getWeight() {
System.out.println("父类普通方法-getWeight");
return new Double("120.5");
}
}
/**
* @Description TODO
* <p>
* Copyright @ 2022 Shanghai Mise Co. Ltd.
* All right reserved.
* <p>
* @Author LiuMingFu
* @Date 2024/1/3 09:56
*/
public class Son extends Father{
public static void main(String[] args) {
System.out.println("子类静态main方法");
Son father = new Son();
System.out.println("weight=" + father.getWeight());
System.out.println("===================================再次触发对象实例化过程===================================");
Son father2 = new Son();
System.out.println("===================================再次触发对象实例化过程===================================");
Son father3 = new Son();
}
public Son() {
System.out.println("子类构造方法!");
}
String job;
{
System.out.println("子类普通代码块-1,job=" + job + ",name=" + name + ",age=" + age + ",sex=" + sex + ",height=" + height);
job = getJob();
}
static int age;
static String name;
static {
System.out.println("=========================start-强行在子类初始化的静态代码块中调用父类、子类对象实例化操作,可以但不规范,需要对jvm有很深的理解才建议运用=========================");
Father father = new Father();
Son son = new Son();
System.out.println("=========================end-强行在子类初始化的静态代码块中调用父类、子类对象实例化操作,可以但不规范,需要对jvm有很深的理解才建议运用=========================");
System.out.println("子类静态代码块-1,age=" + age + ",name=" + name);
age = 20;
name = "李四";
}
Double weight = 99.0d;
{
System.out.println("子类普通代码块-2,weight=" + weight + ",job=" + job + ",name=" + name + ",age=" + age + ",sex=" + sex + ",height=" + height);
weight = getWeight();
}
static String sex = getSex();
static {
System.out.println("子类静态代码块-2,sex=" + sex + "age=" + age + ",name=" + name);
}
static Double height = getHeight();
static {
System.out.println("子类静态代码块-3,height=" + height + "sex=" + sex + "age=" + age + ",name=" + name);
}
public static String getSex() {
System.out.println("子类静态方法-getSex");
return "女";
}
public static Double getHeight() {
System.out.println("子类静态方法-geHeight");
return 100.0d;
}
public String getJob() {
System.out.println("子类普通方法-getJob");
return "Python工程师";
}
public Double getWeight() {
System.out.println("子类普通方法-getWeight");
return new Double("120.5");
}
}
父类静态代码块-1,age=0,name=null
父类静态方法-getSex
父类静态代码块-2,sex=男age=20,name=张三
父类静态方法-geHeight
父类静态代码块-3,height=170.0sex=男age=20,name=张三
=========================start-强行在子类初始化的静态代码块中调用父类、子类对象实例化操作,可以但不规范,需要对jvm有很深的理解才建议运用=========================
父类普通代码块-1,job=null,name=张三,age=20,sex=男,height=170.0
父类普通方法-getJob
父类普通代码块-2,weight=99.0,job=Java工程师,name=张三,age=20,sex=男,height=170.0
父类普通方法-getWeight
父类构造方法!
父类普通代码块-1,job=null,name=张三,age=20,sex=男,height=170.0
子类普通方法-getJob
父类普通代码块-2,weight=99.0,job=Python工程师,name=张三,age=20,sex=男,height=170.0
子类普通方法-getWeight
父类构造方法!
子类普通代码块-1,job=null,name=null,age=0,sex=null,height=null
子类普通方法-getJob
子类普通代码块-2,weight=99.0,job=Python工程师,name=null,age=0,sex=null,height=null
子类普通方法-getWeight
子类构造方法!
=========================end-强行在子类初始化的静态代码块中调用父类、子类对象实例化操作,可以但不规范,需要对jvm有很深的理解才建议运用=========================
子类静态代码块-1,age=0,name=null
子类静态方法-getSex
子类静态代码块-2,sex=女age=20,name=李四
子类静态方法-geHeight
子类静态代码块-3,height=100.0sex=女age=20,name=李四
子类静态main方法
父类普通代码块-1,job=null,name=张三,age=20,sex=男,height=170.0
子类普通方法-getJob
父类普通代码块-2,weight=99.0,job=Python工程师,name=张三,age=20,sex=男,height=170.0
子类普通方法-getWeight
父类构造方法!
子类普通代码块-1,job=null,name=李四,age=20,sex=女,height=100.0
子类普通方法-getJob
子类普通代码块-2,weight=99.0,job=Python工程师,name=李四,age=20,sex=女,height=100.0
子类普通方法-getWeight
子类构造方法!
子类普通方法-getWeight
weight=120.5
===================================再次触发对象实例化过程===================================
父类普通代码块-1,job=null,name=张三,age=20,sex=男,height=170.0
子类普通方法-getJob
父类普通代码块-2,weight=99.0,job=Python工程师,name=张三,age=20,sex=男,height=170.0
子类普通方法-getWeight
父类构造方法!
子类普通代码块-1,job=null,name=李四,age=20,sex=女,height=100.0
子类普通方法-getJob
子类普通代码块-2,weight=99.0,job=Python工程师,name=李四,age=20,sex=女,height=100.0
子类普通方法-getWeight
子类构造方法!
===================================再次触发对象实例化过程===================================
父类普通代码块-1,job=null,name=张三,age=20,sex=男,height=170.0
子类普通方法-getJob
父类普通代码块-2,weight=99.0,job=Python工程师,name=张三,age=20,sex=男,height=170.0
子类普通方法-getWeight
父类构造方法!
子类普通代码块-1,job=null,name=李四,age=20,sex=女,height=100.0
子类普通方法-getJob
子类普通代码块-2,weight=99.0,job=Python工程师,name=李四,age=20,sex=女,height=100.0
子类普通方法-getWeight
子类构造方法!
注意:可以试着注释其中某些代码来测试各种情况,建议读者多多体会这两个示例代码的执行结果,从而验证上诉逻辑执行顺序是否正确
注意:JVM的类卸载是一个复杂的过程,涉及到可达性分析、垃圾收集以及类加载器的管理。而且,类卸载并不频繁发生,因为大多数应用程序在运行时都会稳定在一组核心类上,这些类通常不会被卸载。此外,JVM设计时也考虑到性能和稳定性,因此它可能会保守地处理类卸载,以避免不必要的开销和风险。
https://blog.csdn.net/hsz2568952354/article/details/97496917