首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Java 类加载 —— 底层是如何实现的?

  • 25-03-07 20:01
  • 4499
  • 14124
blog.csdn.net

目录

1、加载

2、链接

2.1 验证阶段:验证类是否符合 JVM 规范,安全性检查

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

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

3、初始化

4、类加载常见问题-练习

5、类加载常见问题-练习2


从类的生命周期而言,一个类包括如下阶段:

类加载阶段分类:加载、链接(验证/准备/解析)、初始化(()V方法 / 发生的时机),如下图所示:


1、加载

  • 将类的字节码载入方法区中,内部采用 c++ 的 instanceKlass 描述 java 类,它的重要 field 域有:
    • _java_mirror 即 java 的类镜像,起到桥梁作用,例如对 String 来说,就是 String.class, 作用是把 kclass 暴露给 java 使用
    • _super 即父类
    • _fields 即成员变量
    • _methods 即方法
    • _constants 即常量池
    • _class_loader 即类加载器
    • _vtable 虚方法表

_itable 接口方法表

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

注意:

  • instanceKlass 这样的【元数据】是存储在方法区(1.8后的元空间内),但 _java_mirror是存储在堆中
  • 可以通过HSDB工具查看
实例对象、类镜像、instanceKlass之间的关系

2、链接

链接又可以分为3个小步骤,如下:

2.1 验证阶段:验证类是否符合 JVM 规范,安全性检查

用 UE 等支持二进制的编辑器修改 HelloWorld.class 的魔数,在控制台运行


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

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

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

  1. // 类加载分析 - 演示 final 对静态变量的影响
  2. public class T02_ClassLoad_Final_Static {
  3. // 在准备阶段,仅仅只有分配空间,没有赋值
  4. static int a;
  5. // 在准备阶段,仅仅只有分配空间,赋值是在类的构造方法中
  6. static int b = 10;
  7. // 在准备阶段,20 属于ConstantValue,final+static 修饰的整型变量 在准备阶段就完成了赋值,编译器对其优化了
  8. static final int c = 20;
  9. // 在准备阶段,"hello" 也属于ConstantValue,static+final 修饰的字符串常量 在准备阶段就完成了赋值,编译器对其优化
  10. static final String d = "hello";
  11. // 在准备阶段,如果 static 变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
  12. static final Object e = new Object();
  13. }

上述代码,通过:javap -v T02_ClassLoad_Final_Static.class进行反编译,得到如下字节码。


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

将常量池中的符号引用解析为直接引用。符号引用仅仅只是符号,并不知道类、方法、属性到底在内存的哪个位置;而直接引用就可以确切知道类、方法、属性在内存的中位置。

代码示例如下:

  1. // 解析的含义
  2. public class T03_ClassLoad_Resolved {
  3. public static void main(String[] args) throws ClassNotFoundException, IOException {
  4. ClassLoader classLoader = T03_ClassLoad_Resolved.class.getClassLoader();
  5. // 可以通过 HSDB工具进行查看:java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
  6. // loadClass 方法不会导致类的解析和初始化: JVM_CONSTANT_UnresolvedClass com.jvm.t09_class_load.D
  7. // D类没有被加载、解析
  8. Class c = classLoader.loadClass("com.jvm.t09_class_load.C");
  9. // 下述会导致C类/D类的加载,JVM_CONSTANT_Class com.jvm.t09_class_load.D
  10. // new C();
  11. System.in.read();
  12. }
  13. }
  14. // 类C
  15. class C {
  16. D d = new D();
  17. }
  18. // 类D
  19. class D {
  20. }

3、初始化

()v 方法,初始化是类加载的最后一个阶段

初始化即调用 ()v,虚拟机会保证这个类的 【构造方法】的线程安全。

发行的时机

概括的说,类初始化是【懒惰的】,下面是会导致类初始化的情况

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

不会导致类初始化的情况

  • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
  • 类对象 .class 不会触发初始化,因为 .class 在加载阶段就已经生成,所以不会被触发
  • 创建该类的数组不会触发初始化
  • 类加载器的 loadClass 方法
  • Class.forName 的参数 2 initialize为 false 时
  1. public class T04_ClassLoad_Init {
  2. static {
  3. // main 方法所在的类,总会被首先初始化
  4. System.out.println("main init");
  5. }
  6. public static void main(String[] args) throws ClassNotFoundException {
  7. // 不会导致类初始化的情况
  8. // 1、静态常量不会触发初始化
  9. System.out.println(B.b);
  10. // 2、类对象.class 不会触发初始化
  11. System.out.println(B.class);
  12. // 3、创建该类的数组不会触发初始化
  13. System.out.println(new B[0]);
  14. // 4、不会初始化类 B,但会加载 B、A
  15. ClassLoader c1 = Thread.currentThread().getContextClassLoader();
  16. c1.loadClass("com.jvm.t09_class_load.B");
  17. // 5、不会初始化类 B,但会加载 B、A
  18. ClassLoader c2 = Thread.currentThread().getContextClassLoader();
  19. Class.forName("com.jvm.t09_class_load.B", false, c2);
  20. // 导致类初始化的情况
  21. // 1、首次访问这个类的静态变量或静态方法时
  22. System.out.println(A.a);
  23. // 2、子类初始化,如果父类还没有初始化,会引发
  24. System.out.println(B.c);
  25. // 3、子类访问父类静态变量,只触发父类初始化
  26. System.out.println(B.a);
  27. // 4、会初始化类 B,并先初始化类 A
  28. Class.forName("com.jvm.t09_class_load.B");
  29. }
  30. }
  31. class A {
  32. static int a = 0;
  33. static {
  34. System.out.println("a init");
  35. }
  36. }
  37. class B extends A {
  38. final static double b = 5.0;
  39. static boolean c = false;
  40. static {
  41. System.out.println("b init");
  42. }
  43. }

4、类加载常见问题-练习

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

  1. // 类加载分析 - 练习
  2. public class T05_ClassLoad_Practice {
  3. public static void main(String[] args) {
  4. System.out.println(E.a);
  5. System.out.println(E.b);
  6. System.out.println(E.c);
  7. }
  8. }
  9. class E {
  10. // a, b 是静态常量,一个是整型,一个字符串常量。是在类链接的准备阶段就已经完成了赋值
  11. public static final int a = 10;
  12. public static final String b = "hello";
  13. // c 是包装类型,20进行装箱,c 是在初始化阶段完成赋值
  14. public static final Integer c = 20; // Integer.valueOf(20)
  15. static {
  16. System.out.println("init E");
  17. }
  18. }

5、类加载常见问题-练习2

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

  1. // 类加载分析 - 练习2
  2. public class T05_ClassLoad_PracticeV2 {
  3. public static void main(String[] args) {
  4. //Singleton.test(); // 调用静态方法test,不会触发类的初始化
  5. Singleton.getInstance(); //
  6. }
  7. }
  8. class Singleton {
  9. public static void test() {
  10. System.out.println("test");
  11. }
  12. private Singleton() {}
  13. // 静态内部类,好处就是可以访问外部类的资源:构造方法,虽然构造方法是私有的,但在静态内部类还是可以访问的
  14. // 内部类中保证单例
  15. private static class LazyHolder {
  16. private static final Singleton SINGLETON = new Singleton();
  17. static {
  18. System.out.println("lazy holder init");
  19. }
  20. }
  21. // 第一次调用,getInstance 方法,才会导致内部类加载和初始化其静态成员
  22. public static Singleton getInstance() {
  23. return LazyHolder.SINGLETON;
  24. }
  25. }

以上实现的特点是:

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

 


文章最后,给大家推荐一些受欢迎的技术博客链接:

  1. Hadoop相关技术博客链接
  2. Spark 核心技术链接
  3. JAVA相关的深度技术博客链接
  4. 超全干货--Flink思维导图,花了3周左右编写、校对
  5. 深入JAVA 的JVM核心原理解决线上各种故障【附案例】
  6. 请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”
  7. 聊聊RPC通信,经常被问到的一道面试题。源码+笔记,包懂

 


欢迎扫描下方的二维码或 搜索 公众号“10点进修”,我们会有更多、且及时的资料推送给您,欢迎多多交流!

                                           

       

注:本文转载自blog.csdn.net的不埋雷的探长的文章"https://blog.csdn.net/weixin_32265569/article/details/108037510"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top