JVM 类加载

类加载阶段

为了方便记忆,我们可以使用一句话来表达其加载的整个过程,“家宴准备了西式菜”,即家(加载)宴(验证)准备(准备)了西(解析)式(初始化)菜。保证你以后能够很快的想起来。

1 加载

将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:

  • _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
  • _super 即父类
  • _fields 即成员变量
  • _methods 即方法
  • _constants 即常量池
  • _class_loader 即类加载器
  • _vtable 虚方法表
  • _itable 接口方法表

如果这个类还有父类没有加载,先加载父类
加载和链接可能是交替运行的

  • 注意
    instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),但 _java_mirror是存储在堆中
  • 可以通过前面介绍的 HSDB 工具查看

2 链接

验证

验证类是否符合 JVM规范,安全性检查
用 UE 等支持二进制的编辑器修改 HelloWorld.class 的魔数,在控制台运行

E:\git\jvm\out\production\jvm>java cn.itcast.jvm.t5.HelloWorld Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 3405691578 in class file cn/itcast/jvm/t5/HelloWorld       at java.lang.ClassLoader.defineClass1(Native Method)       at java.lang.ClassLoader.defineClass(ClassLoader.java:763)       at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)       at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)       at java.net.URLClassLoader.access$100(URLClassLoader.java:73)       at java.net.URLClassLoader$1.run(URLClassLoader.java:368)       at java.net.URLClassLoader$1.run(URLClassLoader.java:362)       at java.security.AccessController.doPrivileged(Native Method)       at java.net.URLClassLoader.findClass(URLClassLoader.java:361)       at java.lang.ClassLoader.loadClass(ClassLoader.java:424)       at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)       at java.lang.ClassLoader.loadClass(ClassLoader.java:357)       at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

准备
为 static 变量分配空间,设置默认值

  • static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
  • static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
  • 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
  • 如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成

解析

将常量池中的符号引用解析为直接引用

3 初始化

<cinit>()V 方法

初始化即调用 ()V ,虚拟机会保证这个类的『构造方法』的线程安全

发生的时机

概括得说,类初始化是【懒惰的】

  • main 方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量或静态方法时
  • 子类初始化,如果父类还没初始化,会引发
  • 子类访问父类的静态变量,只会触发父类的初始化
  • Class.forName
  • new 会导致初始化

不会导致类初始化的情况

  • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
  • 类对象.class 不会触发初始化
  • 创建该类的数组不会触发初始化

实验

class A {    static int a = 0;        static {        System.out.println("a init");   } } class B extends A {    final static double b = 5.0;    static boolean c = false;    static {        System.out.println("b init");   } }

验证(实验时请先全部注释,每次只执行其中一个)

public class Load3 {    static {        System.out.println("main init");   }    public static void main(String[] args) throws ClassNotFoundException {        // 1. 静态常量(基本类型和字符串)不会触发初始化        System.out.println(B.b);        // 2. 类对象.class 不会触发初始化        System.out.println(B.class);        // 3. 创建该类的数组不会触发初始化        System.out.println(new B[0]);        // 4. 不会初始化类 B,但会加载 B、A        ClassLoader cl = Thread.currentThread().getContextClassLoader();        cl.loadClass("cn.itcast.jvm.t3.B");        // 5. 不会初始化类 B,但会加载 B、A        ClassLoader c2 = Thread.currentThread().getContextClassLoader();        Class.forName("cn.itcast.jvm.t3.B", false, c2);        // 1. 首次访问这个类的静态变量或静态方法时        System.out.println(A.a);        // 2. 子类初始化,如果父类还没初始化,会引发        System.out.println(B.c);        // 3. 子类访问父类静态变量,只触发父类初始化        System.out.println(B.a);        // 4. 会初始化类 B,并先初始化类 A        Class.forName("cn.itcast.jvm.t3.B");   } }

4 练习

从字节码分析,使用 a,b,c 这三个常量是否会导致 E 初始化

public class Load4 {    public static void main(String[] args) {        System.out.println(E.a);        System.out.println(E.b);        System.out.println(E.c);   } } class E {    public static final int a = 10;    public static final String b = "hello";    public static final Integer c = 20; }

典型应用 - 完成懒惰初始化单例模式

public final class Singleton {    private Singleton() { }    // 内部类中保存单例    private static class LazyHolder {        static final Singleton INSTANCE = new Singleton();   }    // 第一次调用 getInstance 方法,才会导致内部类加载和初始化其静态成员    public static Singleton getInstance() {        return LazyHolder.INSTANCE;   } }

以上的实现特点是:

  • 懒惰实例化
  • 初始化时的线程安全是有保障的

5 类加载器

以 JDK 8 为例:

名称 加载哪的类 说明
Bootstrap ClassLoader JAVA_HOME/jre/lib 无法直接访问
Extension ClassLoader JAVA_HOME/jre/lib/ext 上级为 Bootstrap,显示为 null
Application ClassLoader classpath 上级为 Extension
自定义类加载器 自定义 上级为 Application

启动类加载器

扩展类加载器

双亲委派模式

自定义类加载器


__EOF__

本文作者飞飞很要强
本文链接https://www.cnblogs.com/LiPengFeiii/p/15216065.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   飞飞很要强  阅读(46)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示