JVM-ClassLoader类加载器

本文详细介绍了JVM的加载机制,包括加载、链接和初始化三个关键阶段,以及这些过程是如何被触发的。深入探讨了类加载器的工作原理,类如何在运行时被加载和创建,以及初始化过程中涉及的关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

目标

what

JVM启动过程概述

loading and creation阶段

Linking 链接

initialization初始化

几个过程的关系图


目标

详细介绍类加载器的3个阶段。

what

JVM 动态的loading加载 linking链接 initializing初始化 class and interfacese,其不是一次串行执行,而是分阶段触发的

JVM启动过程概述

通过使用bootstrap class loader 加载和创建 初始化类initial class 的方式启动JVM,之后JVM链接并初始化这个类,并调用该类的public static void main方法。main方法将驱动后续的执行,执行main方法中的JVM指令,将引起JVM去加载链接初始化其他的类和接口。

loading and creation阶段

作用

加载与创建,输出Class object

概述

JVM通过[类或接口的全路径名称]+[定义类加载器]找到对应的字节码表现形式(.class),然后JVM使用字节码来创建classes和interfaces

前置知识

类或接口被加载的原因

类或接口C的创建,是其他的类或接口D触发的,即D在运行时使用C时。

  • 运行时,D的code使用jvm指令直接调用常量池中有C的符号引用,引起D去解析C,
  • 运行时,D通过JavaSE的反射机制加载C。

类/接口/数组是由谁创建的

  • 类和接口是使用class loader加载其字节码进行加载创建的;
  • 数组是JVM创建,不是加载器。

类加载器分两种

  • 由JVM提供的bootstrap类加载器
  • 用户定义的类加载器
    • 继承抽象类ClassLoader
    • 用于扩展JVM的动态加载;用于从特定的资源(如特定的package,network,加密文件等)中加载类;

定义类加载器与初始化类加载器

  • 定义类加载器
    • 完成class C加载和定义的类加载器L,则L是C的定义类加载器
  • 初始化类加载器
    • 参与class C的加载过程的所有类加载器,都称为C的初始化类加载器。

运行时,如何唯一确定类和接口

(类或接口C的全路径名N,定义类加载器L)

 

加载和创建全路径N关联的类或接口C的原则

C不是数组

使用用户定义类加载器进行类加载

  • JVM判断L是否被记录为[由全路径N表示的类或接口C]的初始化类加载器,如果是则直接返回C;
  • 否则JVM将调用L的loadClass方法,如果加载成功,JVM会记录L为C的初始化类加载器。
  • 举例,loadClass默认实现(双亲委派)
    • 查询加载记录(findLoadedClass(name)),如果找到则直接返回C
    • 如果不是,则L委派给parent类加载器或bootstrap类加载器,如果成功加载C,则返回C;
    • 如果未加载到C,则L将在其负责的资源范围内进行加载,如果成功加载,则返回C;
    • 如果未加载,则抛出ClassNotFoundException。

使用bootstrap类加载器进行类加载

  • JVM判断L是否被记录为[由全路径N表示的类或接口C]的初始化类加载器,如果是则直接返回C;
  • 如果不是,则JVM调用bootstrap类加载器的方法,在OS的文件系统中搜索C的表现形式。找不到则抛出ClassNotFoundException。
  • JVM通过找到的表现形式生成类或接口C

C是数组

由JVM直接创建数组class;在创建数组的过程中,数组成员类型T的定义类加载器将加载创建T。

  • JVM判断L是否为[成员类型为T的数组class C]的初始化类加载器,如果是则直接返回C
  • 如果成员类型T为引用类型,则使用L来加载T
  • JVM将依据[成员类型T]和成员数量N来创建array class
  • JVM记录L为成员类型T的初始化类加载器

 

class file如何派生出Class

  • JVM判断L是否已经被记录为[由N表示的C]的初始化类加载器,如果是,则这个创建操作是非法的,抛出LinkedError
  • JVM尝试解析C的class file表现形式
    • 如果不是class file结构,则抛出ClassFormatError
    • 如果class file的版本不在支持的范围内,则抛出UnSupportedClassVersionError
    • 如果表现形式并不能代表[N表示的C],则抛出NotClassDefFoundError
  • 如果C具有直接父类superclass,则通过superclass的符号引用去load和create父类;
  • 如果C具有superinterfaces,则通过符号引用去load和create superinterface
  • JVM标记C的定义类加载器为L,并记录L是C的初始化类加载器。

 

loadClass过程图示

 

Linking 链接

概述

JVM通过[验证verifying/准备preparing class或者interface以及superclass以及superinterface,(如果是数组calss的话,将包括元素类型)],解决class或interface中的符号引用,将class或者interface组装到运行时run-time state,以便可以被执行。

Linking的过程涉及分配内存空间,可能引起OOM

步骤

verifying

验证字节码表现形式是否结构化正确。验证会引起其他的class或接口被load,但是不会去验证和准备它们。

preparing

创建类或接口的static field,使用系统默认值初始化static field

resolution 贯穿代码执行的持续性阶段

什么时候进行resolution?

class的初始化过程和方法执行时,某些jvm指令被执行,对指令使用的class或接口的符号引用进行解析。

指令:anewarray, checkcast, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, putfield, and putstatic

详情

  • 类或接口的解决方式,如D中引用了C
    • 使用D的定义类加载器来load和creat C
    • 如果C是数组且元素类型是引用类型,则使用D的定义类加载器来load和create该元素类型
    • 检查D对C的可访问性,可能抛出IllegalAccessError
  • 域field的解决方式,如D中引用了C的field
    • 解决类或接口C
    • 遍历C以及super中的field
      • 在C声明的域中找到匹配的field
      • 否则从superinterface中匹配
      • 否则从superclass中匹配
      • 否则匹配失败, 抛出NoSuchFieldError
      • 如果找到field则检查可访问性,若不可访问则抛出IllegalAccessError
  • 方法的解决方式,如D中引用了类C的method
    • 解决类或接口C
    • 检查C是类还是接口,若是接口,则抛出IncompatibleClassChangeError
    • 遍历C以及super中的method
      • C声明了一个相同名称的多态方法
      • C声明了一个具有相同名称和方法签名的方法
      • 如果C有superclass,则针对superclass进行3.1
      • 如果C有superinterface,则在superinterface中找
      • 如果找不到,则抛出NoSuchMethodError
      • 如果方法找到了,但是抽象方法,则抛出AbstractMethodError
      • 如果方法找到了,但是D不可以访问这个方式,则抛出IllegalAccessError

access control访问控制

D可访问C,需满足其中一个条件

  • C是public
  • C和D在同一个package中。

D可访问域或者方法R,需满足其中一个条件

  • R是public
  • R是protected且属于C,需要D是C的子类或者D=C
  • R是protected或默认访问性(包级别访问性)且属于C,需要D和C在同一个package中
  • R是private且属于D

method overrided方法重写,类C的m1重写了类A的m2方法

C是A的子类,m1与m2具有相同的方法名称和方法声明(m1的访问性<=m2的访问性)

 

initialization初始化

概念

通过执行class或者interface的初始化方法<clinit>,来初始化class或者interface

‘类或者接口的初始化’的触发原因

  • 引用了类或接口的JVM指令被执行:new/getstatic/putstatic/invokesatic
    • 执行new指令时,如果符号引用关联的类或接口没有被初始化过,则进行初始化。
    • 执行getstatic/putstatic/invokestatic时,声明了这个field或method的类如果没有被初始化,则进行初始化
  • java.lang.invoke.MethodHandler实例的第一次调用
  • 反射库中具体反射方法的调用
  • 子类被初始化
  • 作为JVM start-up的初始化类

JVM是多线程,如何保证同步的进行初始化呢?

  • 已经完成验证和准备阶段的Class object,可能有如下4种状态

        Class object已经被检查和准备,未初始化
        Class object正在被某个具体线程进行初始化
        Class object已经初始化成功
        Class object处于错误状态,可能是初始化正在被执行或者失败

  • 对一个具体的类或接口,会对应唯一的初始化锁LC;由JVM负责C与LC的关系;
  • 初始化的步骤(指的不是类构造器)
    • 在LC上进行同步。当前线程在获取到LC之前会一直waiting
    • 如果Class object表明:其初始化正由另一个线程进行。则当前线程会release LC,然后等待被通知这个初始化过程已经完成,然后重复上一步骤。
    • 如果Class object表明:其初始化正由当前线程进行。则表明本次递归请求,则release LC并正常结束。
    • 如果Class object表明:其初始化已经完成了。则无后续动作,直接release LC并正常结束。
    • 如果Class object处于一个错误状态,则表示无法进行初始化,release LC并抛出NoClassDefFoundError
    • 上述情况都不成立的话,则记录下Class object的初始化正由当前线程执行,然后release LC
    • 按照在classfile中出现的顺序,使用常量数据初始化C中[final static field]
    • 递归的进行supercalss和superinterface的初始化过程(所以父类的static块优先执行)
    • 执行C的初始化方法<clinit>
    • 如果C的初始化方法正常完成,则获取LC,设置Class object已经初始化完毕,notify所有等待的线程,release LC,完成这个过程。
    • 如果C的初始化方法由于异常E而僵硬的完成,如果E不是error,则创建ExceptionInInitializerError,进行next step;如果E是OOM,则next step
    • 获取LC,设置Class object初始化失败,release LC

几个过程的关系图

 

类加载器--命名空间--共享访问--安全

  • 每个类加载器都有一个独立的命名空间。
  • 命名空间概念:虚拟机中存有加载器A的一张表,该表记录了将A视为初始类加载器的所有类型,该表极为A的命名空间。
  • 在虚拟机中加载的类是唯一的,这须由加载器命名空间和类全路径名来一起作为限制。
  • 类加载器采用双亲委派方式来使用合适的加载器进行加载工作。
  • 真正进行加载工作的成为定义类加载器,而之前发起委派的以及定义类加载器都称为初始类加载器。
  • 被加载的类A在其初始类加载器B,C,...中共享访问的。
  • 加载类A后生成如下约束
    • 加载器B是类型A的初始类加载器,加载器C是类型A的初始类加载器,并且这两个类型A是同一个类型。当恶意添加某同名A类(可能输出重要数据)以及重写加载器B(或C)时,这个约束会发现当前加载器B加载的类A和之前加载类A不是一个类型,从而提示错误。如果没有该约束,那么A被加载,重要数据被输出。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值