类的生命周期

  • 1. 类的生命周期
  • 1.1 加载
  • 1.2 连接
  • 1.2.1 验证
  • 1.2.2 准备
  • 1.2.3 解析
  • 1.3 初始化(静态模块执行,执行一次)
  • 1.3.1 类的初始化时机(类的主动使用):
  • 1.3.2 类的初始化顺序:
  • 1.4 使用
  • 1.4.1 对象实例化(实例化之后可以被正常使用)
  • 1.5 卸载


JVM的类加载流程就是解析java的class文件,将其加载到内存中,并对数据进行校验、转换解析和初始化;最终变成可以直接被JAVA虚拟机使用的数据。

1. 类的生命周期

action生命周期 java java 类生命周期_初始化


java为了支持运行时绑定,解析过程在某些情况下是在初始化之后才开始的,其他过程是不可变的。

1.1 加载

  1. 通过全限定类名获取这个类的二进制文件流。
  2. 将这个字节流所代表的静态存储结构转化成方法区的运行时结构。
  3. 在内存中生成一个代表这个类的java.lang.String 对象,作为访问这个类的的入口。

1.2 连接

1.2.1 验证

验证是连接过程的第一步,主要保证这些数据符合java虚拟机的规范,不会危害到虚拟机自身的安全。

  1. 文件格式验证:魔数、主版本号、次版本号是否在虚拟机的处理范围之内,常量是否合理,字节流是否可以正常解析。
  2. 元数据验证:父类与子类间的继承链是否正常,比如方法、字段是否冲突,子类是否实现了父类接口中要求需要实现的方法。是否存在不合法的元数据。
  3. 字节码验证:通过数据流和控制流分析,确保程序语义合法、符合逻辑,跳转指令在方法体之内。
  4. 符号引用验证:保证可以将符号引用转化成直接引用。

1.2.2 准备

为类变量分配内存并设置初始化值,这些变量所使用的内存都将在方法区中进行分配。

1.2.3 解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行

1.3 初始化(静态模块执行,执行一次)

1.3.1 类的初始化时机(类的主动使用):

  1. 创建类的实例;
  2. 访问某个类或接口的静态变量(无重写的变量继承,变量其属于父类,而不属于子类),或者对该静态变量赋值(静态的read/write操作);
  3. 调用类的静态方法;
  4. 反射(如:Class.forName(“com.test.Test”));
  5. 初始化一个类的子类(Chlidren 继承了Parent类,如果仅仅初始化一个Children类,那么Parent类也是被主动使用了);
  6. Java虚拟机启动时被标明为启动类的类(换句话说就是包含main方法的那个类,而且本身main方法就是static的);
  7. JDK1.7开始提供的动态语言的支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_public,REF_invokeStatic句柄对应的类没有初始化,则初始化。

1.3.2 类的初始化顺序:

action生命周期 java java 类生命周期_System_02


action生命周期 java java 类生命周期_action生命周期 java_03

package com.jon;
/**
 * 有继承关系的类初始化顺序
 * @author suym
 * @date   2021/7/23
 *
 */
class Parent {
    // 静态变量
    public static String P_STATIC_FIELD = "父类--静态变量";
    protected int i = 1;
    protected int j = 8;
    // 变量
    public String pField = "父类--变量";
    // 静态初始化块
    static {
        // 静态变量必定在静态代码块之前
        System.out.println(P_STATIC_FIELD);
        System.out.println("父类--静态初始化代码块");
    }
    // 初始化块
    {
        // 变量必定在初始化代码块之前
        System.out.println(pField);
        System.out.println("父类初始化代码块");
    }
    // 构造器
    public Parent() {
        // 构造方法与构造代码块之间谁在前,谁在后
        System.out.println("父类--构造器");
        System.out.println("i=" + i + ", j=" + j);
        j = 9;
    }
}

public class SubClass extends Parent {
    // 静态变量
    public static String S_STATIC_FIELD = "子类--静态变量";
    // 变量
    public String sField = "子类--变量";
    // 静态初始化块
    static {
        // 静态变量必定在静态代码块之前
        System.out.println(S_STATIC_FIELD);
        System.out.println("子类--静态初始化块");
    }
    // 初始化块
    {
        // 变量必定在初始化代码块之前
        System.out.println(sField);
        System.out.println("子类--初始化块");
    }

    // 构造器
    public SubClass() {
        // 构造方法与构造代码块之间谁在前,谁在后
        System.out.println("子类--构造器");
        System.out.println("i=" + i + ",j=" + j);
    }
    // 程序入口
    public static void main(String[] args) {
        new SubClass();
    }
}

action生命周期 java java 类生命周期_action生命周期 java_04

1.4 使用

类的使用分为主动使用和被动使用,主动使用的时候也是初始化的时机。
主动使用会进行初始化,被动使用不会进行初始化,比如:调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化。

1.4.1 对象实例化(实例化之后可以被正常使用)

类初始化之后才能进行对象实例化,对象实例化是对象生命周期的开始。两者区别如下:

类初始化:是完成程序执行前的准备工作。在这个阶段,静态的(变量,方法,代码块)会被执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。

类的实例化:是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存

action生命周期 java java 类生命周期_初始化_05

1.5 卸载

一个类如果满足以下条件时,类就会被卸载

  1. 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
  2. 加载该类的ClassLoader已经被回收。
  3. 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。