JVM-ClassLoader类加载器

目录

目标

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被加载,重要数据被输出。

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值