【Java 虚拟机原理】Java 类中的类加载初始化细节 ( 只使用类中的常量时加载类不会执行到 ‘初始化‘ 阶段 )

简介: 【Java 虚拟机原理】Java 类中的类加载初始化细节 ( 只使用类中的常量时加载类不会执行到 ‘初始化‘ 阶段 )

文章目录

一、类加载初始化时机

二、常量加载示例

三、数组加载示例





一、类加载初始化时机


类加载时机 : Java 程序执行时 , 并不是一开始将所有的字节码文件都加载到内存中 , 而是用到时才进行加载 ;


通过 new 关键字创建实例对象 ;

通过 Class 反射 获取类 ; 如 : Class.forName(“Xxx”) 获取类 ;

序列化 / 反序列化 ;

调用 clone 克隆对象 ;

有 main 函数的类 , 会默认自动加载 ;

调用子类 , 如果之前没有加载过父类 , 则 自动加载父类 ;

访问 类 的 静态变量

image.png


有些类加载操作 , 不需要执行 加载 -> 连接 ( 验证 , 准备 , 解析 ) -> 初始化 这个完整的流程 ;


如 : 如果是 public final static 修饰的常量值 , 在编译阶段 , 就会将该值放到常量池中 ; 在类加载的过程中 , 只要执行到 加载 -> 连接 ( 验证 , 准备 , 解析 ) 阶段 , 就可以完成常量池的初始化 , 即使没有执行 初始化 这个步骤 , 也不影响使用类中的常量值 ;



在 连接 的 准备 阶段 , 为 普通 的 静态变量 进行 默认赋值 , 但是针对 静态常量 , 直接进行 指定赋值 ;


但是 普通的 静态变量 的 指定赋值 , 是在 初始化 阶段 完成的 ;



类 在 " 初始化 " 阶段 , 调用 静态代码块 ;






二、常量加载示例


类加载时 , 如果只用到了类中的常量 , 则只进行 " 加载 -> 连接 ( 验证 , 准备 , 解析 ) " 两个过程 :


public class Student {
    // 常量
    public static final int age = 18;
    static {
        // 加载类的 " 初始化 " 阶段才执行 静态代码块
        //  如果只是进行了 " 连接 " , 没有进行 初始化 , 则不会调用该代码块
        System.out.println("Student 静态代码块调用");
    }
}


主函数 :


public class Main {
    public static void main(String[] args) {
        int age = Student.age;
        System.out.println("main 函数 age = " + age);
    }
}


执行结果 :


image.png



上述 Student 类中的 静态代码块 没有被执行 , 说明 类加载 的流程中 , " 初始化 " 步骤 , 没有被执行 ;


找到 Student.class 字节码文件 , 然后使用


javap -v -Student.class


查看该字节码文件的附加信息 ;


在 " 常量表 " 中 , 发现了常量值 18 1818 , 这个常量值是在编译阶段就编译到了字节码中 ; 在 " 连接 " 的 " 准备 " 阶段 , 该常量值就设置完毕 ; 出于最大限度性能优化的考虑 , 如果不使用该类的其它值 , 就不会执行 " 初始化 " 阶段 ; 因此这里不会调用 静态代码块 中的代码 ;


Constant pool:
  #10 = Integer            18


image.png





三、数组加载示例


对数组进行创建操作 , 如创建了一个对象数组 , 此时不会加载该对象对应的类 , 只会为其在内存分配空间 ;


创建数组时 , 触发的是 Student[] 数组类型的 类加载初始化 , 但是不会触发 Student 类的初始化操作 ;


如果调用数组中的元素时 , 就需要初始化 Student 类 ;



Student 类 :


public class Student {
    // 常量
    public static final int age = 18;
    static {
        // 加载类的 " 初始化 " 阶段才执行 静态代码块
        //  如果只是进行了 " 连接 " , 没有进行 初始化 , 则不会调用该代码块
        System.out.println("Student 静态代码块调用");
    }
}


main 函数 :


public class Main {
    public static void main(String[] args) {
        System.out.println("main 函数执行");
        Student[] students = new Student[2];
    }
}


执行结果 : 只是创建数组 , 只需要为其分配内存空间 , 不需要为每个 Student 数组元素赋值 , 这里不需要初始化 Student 类 ;


image.png

目录
相关文章
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
507 2
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
443 0
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
747 24
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
1137 13
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
198 1
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
503 0
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
1224 55
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
1182 6
|
12月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
723 5