00-JAVA基础-JVM类加载机制及自定义类加载器

JVM 类加载机制

JVM类加载机制是Java运行时环境的核心部分,它负责将类的.class文件加载到JVM中,并将其转换为可以被JVM执行的数据结构。

类加载的整体流程

类加载的整体流程可以分为五个阶段:加载(Loading)、链接(Linking)、初始化(Initialization)、使用和卸载(Unloading)。其中,链接阶段又可以细分为验证(Verification)、准备(Preparation)和解析(Resolution)三个阶段。

加载阶段

  • 系统提供的类加载器或自定义类加载器:首先,通过类加载器(可以是系统提供的,也可以是用户自定义的)根据类的全名(包括包名)找到对应的.class文件。
  • 读取.class文件:类加载器从文件系统或网络等位置读取.class文件的二进制数据。
  • 创建Class对象:将读取到的二进制数据转换为方法区的运行时数据结构,并在堆区创建一个java.lang.Class对象,这个Class对象作为该类在JVM中的元数据表示。

链接阶段

  • 验证阶段:
    • 文件格式验证:验证.class文件是否符合JVM规范,是否是一个有效的字节码文件。
    • 元数据验证:验证字节码中的元数据是否符合Java语言规范。
    • 字节码验证:验证字节码的执行是否符合Java虚拟机规范。
    • 符号引用验证:验证类中的符号引用是否有效,能否被正确解析。
    • 准备阶段:为类的静态变量分配内存空间,并设置初始值(注意,这里的初始值不是代码中显式赋予的值,而是根据变量的数据类型赋予的默认值,如int为0,引用类型为null)。
    • 解析阶段:将常量池中的符号引用转换为直接引用。符号引用是一个抽象的概念,如字段名、方法名等,而直接引用则是指向内存中的具体地址。

初始化阶段

  • 初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有变量的赋值动作和静态代码块(static代码块)中的语句合并产生的。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先初始初始化其父类。
  • JVM会保证一个类的()方法在多线程环境中被正确的加锁和同步。因此JVM中一个类的class是线程安全的。
  • 只有当类或接口的静态变量被首次主动使用时,JVM才会初始化这个类或接口。
类的主动引用(一定会发生类的初始化
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当JVM启动,java Hello,则一定会初始化Hello类(即先启动main方法说在的类)
  • 当初始化一个类,如果其父类没有被初始化,则会先初始化它的父类

注意:
一个类被初始化后,不会重复进行被初始化

类的被动引用(不会发生类的初始化
  • 当访问一个静态属性是,只有真正声明这个域的类才会被初始化
    • 通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(因为常量在编译节点就存入调用类的常量池中了

使用和卸载阶段

  • 类被初始化后,就可以被JVM使用,创建实例对象、调用方法等。
  • 当类不再被使用时,JVM会将其从内存中卸载,释放其占用的资源。

测试类的加载机制

创建一个A对象和一个B对象,使B对象继承A,测试时初始化A,查看JVM类的加载过程

A.java

package demo;

public class A {
   
   

    /** 定义一个静态属性 */
    public static String NAME = "A";

    /** 定义一个final 静态常量 */
    public static final int AGE = 20;

    static {
   
   
        System.out.println("A static 代码块被调用了");
        NAME = "static 修改了 NAME";
    }

    public A(){
   
   
        System.out.println("A 默认构造方法被调用了");
    }

}

B.java

package demo;

public class B extends A {
   
   

    /** 定义一个静态属性 */
    private static String TYPE = "A";

    /** 定义一个final 静态常量 */
    private static final int WIDTH = 20;

    static {
   
   
        System.out.println("B static 代码块被调用了");
        TYPE = "static 修改了 TYPE";
    }

    public B(){
   
   
        System.out.println("B 默认构造方法被调用了");
    }

}

ClassLoadDemo.java

package demo;

/**
 * 测试JVM类的加载机制
 *
 * @author Anna.
 * @date 2024/4/5 14:14
 */
public class ClassLoadDemo {
   
   

  static {
   
   
    System.out.println("mian方法所在类的 static 代码块被调用了");
  }

  public static void main(String[] args) {
   
   
    new B();

    System.out.println("初始化完成后-第二次调用不会重复进行初始化");
    new B();
  }
}

执行结果:

在这里插入图片描述

结论:

一个类初始化时,如果其父类没有被初始化,则先初始化其父类。

一个类被初始化后,再次被引用时,则不会重复初始化。

new 对象会默认调用类的无参构造方法

测试类的主动引用,一定会发生类的初始化

类定义使用上述A.java及B.java

  • 通过new一个对象
public class ClassLoadDemo1 {
   
   

  public static void main(String[] args) {
   
   
    System.out.println("========1 通过new========");
    new A();
  }
}

执行结果:
在这里插入图片描述

  • 调用类的静态成员(除了final常量)和静态方法
package demo;

public class ClassLoadDemo1 {
   
   

  public static void main(String[] args) {
   
   
    System.out.println("========2 调用类的静态成员(除了final常量)和静态方法========");
    System.out.println("调用final常量:");
    System.out.println("A.AGE:" + A.AGE);
    System.out.println("调用非final静态成员常量:");
    System.out.println("A.NAME:" + A.NAME);
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丨Anna丨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值