Java 类加载和类加载器

亦凉 2023-09-26 23:44 20阅读 0赞

类加载


Java 的类加载阶段分为:加载链接初始化,而链接的过程中包括:验证准备解析

加载

将类的字节码载入方法区中,内部采用 C++ 的 instanceKclass 描述 Java 类

instanceKclass 的重要字段:

  • _java_mirror:Java 类镜像,存放类对象的地址,例如对于 String,存放的就是String.class
  • _super:即父类
  • _methods:即方法
  • _constants:即常量池
  • _class_loader:类加载器
  • _vtable:虚方法表
  • _itable:接口方法表

format_png

链接

链接阶段包括验证准备初始化三个部分

验证

验证类是否符合 JVM 规范,进行安全性检查,例如:检查 Java 文件的魔数

准备

static 变量分配地址空间,设置默认值

  • static 变量分配空间赋值是两个步骤,分配空间是在准备阶段完成,而赋值是在初始化阶段完成;
  • 若 static 变量是 final 类型的,且变量类型为基本数据类型或者字符串对象,则在准备阶段进行赋值
  • 若 static 变量是 final 类型的,且变量类型为引用对象,则仍在初始化阶段进行赋值

解析

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

初始化

初始化调用 <cinit>()v即执行类的构造方法,代码块等,虚拟机会保证这个类的线程安全

发生的时机

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

  • main 方法所在的类,总会被首先初始化;
  • 首次访问类的静态变量或者静态方法,因为这些变量不是 final 的,会在类的初始化阶段进行赋值;
  • 子类初始化,会引起父类的初始化;
  • 子类方法父类的静态变量,会导致父类的初始化;
  • Class.forName;
  • new 会导致初始化;

不会触发类的初始化

  • 访问类的 static final 静态变量(基本类型和字符串),不会被初始化,因为这些变量的赋值是在准备阶段;
  • 类对象.class 不会触发类的初始化;
  • 创建该类的数组不会触发初始化;
  • 类加载器的 loadClass 方法;
  • Class.forName 的参数 2 为 false

类加载器


以 JDK8 为例































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

类加载器的作用:加载类的二进制字节码

双亲委派模式

双亲委派模式是一种Java类加载机制,它定义了一种层次化的父子关系,由父类加载器向下委派请求,直到找到合适的类加载器为止。

  1. 首先会检查缓存,查找该加载器是否已经加载过这个类了,如果没有就去父类的加载器中去寻找;
  2. 如果缓存中没有找到,就会自顶而下用的类加载器去创建该类;
  3. 最终返回该类即可;

    protected Class<?> loadClass(String name, boolean resolve)

    1. throws ClassNotFoundException
    2. {
    3. synchronized (getClassLoadingLock(name)) {
    4. // First, check if the class has already been loaded
    5. // 检查缓存,是否已经加载过这个类
    6. Class<?> c = findLoadedClass(name);
    7. if (c == null) {
    8. long t0 = System.nanoTime();
    9. try {
    10. if (parent != null) {
    11. // 向上级的累加器中找
    12. c = parent.loadClass(name, false);
    13. } else {
    14. // 向 Bootstrap 类加载器中找
    15. c = findBootstrapClassOrNull(name);
    16. }
    17. } catch (ClassNotFoundException e) {
    18. // ClassNotFoundException thrown if class not found
    19. // from the non-null parent class loader
    20. }
    21. // 如果缓存没有找到,就去创建
    22. if (c == null) {
    23. // If still not found, then invoke findClass in order
    24. // to find the class.
    25. long t1 = System.nanoTime();
    26. // 调用 findClass 去创建类
    27. c = findClass(name);
    28. // this is the defining class loader; record the stats
    29. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    30. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    31. sun.misc.PerfCounter.getFindClasses().increment();
    32. }
    33. }
    34. if (resolve) {
    35. resolveClass(c);
    36. }
    37. return c;
    38. }
    39. }

作者:EzreaLwj
链接:https://juejin.cn/post/7226665757882236986

发表评论

表情:
评论列表 (有 0 条评论,20人围观)

还没有评论,来说两句吧...

相关阅读

    相关 ——

    虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作

    相关 Java

    类加载 -------------------- Java 的类加载阶段分为:加载、链接、初始化,而链接的过程中包括:验证、准备、解析 加载 将类的字节码载入方

    相关 机制

    一 点睛 1 类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象。 2 当JVM启动时,会形成由