类加载概述

10 篇文章 1 订阅

加载

加载过程主要完成以下几件事情

  • 类加载器根据类的全限定名获取类的二进制字节流
  • 将二进制字节流表示的静态结构转换为方法区的运行时数据结构
  • 在堆上面实例化一个该类的Class对象,作为方法区这个类的各种数据访问的入口

验证阶段

验证是连接阶段的第一步

验证阶段主要目的是确保Class文件的字节流中包含的信息符合java虚拟机规范,确保这些信息被加载
入内存不会危害到jvm自身的安全。主要包含以下几个步骤
  • 文件格式验证

      验证字节流是否符合Claa文件格式的规范要求,确保字节流所表示的静态结构能转换为运行时数据结
      构,验证阶段只有此步骤是基于字节流的,后面的几个验证的步骤就是基于方法区的运行时数据结构
      了
      有以下内容
      
      验证魔数
      验证版本号
      常量池是否有不支持的类型
      CONSTANT_Utf8_info 有不符合Utf-8的编码的数据
      等等等等
    
  • 元数据验证

      对字节码的信息进行语义分析
      这个类是否有父类(除了java.lang.Object外应该全有父类)
      这个类是否继承了不允许被继承的类(final类)
      这个类是否实现了接口中应该实现的所有方法
      等等
    
  • 字节码验证

      对class文件中的code属性进行验证,保障被验证类的方法在运行时不会做出危害虚拟机的行为。
      例如字节码指令对应的操作数栈的数据类型是否一致,不会出现指令是将int类型存入局部变量表而
      操作数栈却弹出long类型的数据存入变量表
    
  • 符号引用验证

      符号引用验证发生在解析阶段,及将符号引用转换为直接引用的过程。确保解析阶段的进行
      主要有以下内容
      根据符号引用描述的全限定类名是否能找到相应的类
      在指定的类中是否有相应的字段或者方法
      检查当前类对该字段和方法的访问权限
    

准备阶段

准备阶段是为类变量分配内存初始化为零值的过程

在准备阶段将为类中变量和类常量在方法区分配内存空间。并且如果是变量则初始化为零值,如果
是常量,则初始化为程序代码中指定的常量值。
值得注意的是,由于方法区相当于一个接口,只是逻辑上的区域。所以在JDK1.7以前类变量在永久
代,在JDK1.8以后类变量就随着Class实例化对象在堆上谜案。

tip:对于类变量的赋值语句,被整合到类构造器中(clinit), 在初始化阶段执行。


解析阶段

解析阶段将常量池中的符号引用转换为直接引用,再解析阶段除了堆符号引用的解析,最终逗得对解析结果进行权限的验证

符号引用;通过一组符号来描述所引用的目标,符号可以是任何形式的字面量只要能无歧义的定位
到目标即可
直接引用:直接指向目标的偏移量,指令或者是可以定位到目标的句柄
  • 类和接口的解析

假设当前代码所处的类为C,要解析一个从未被解析过的符号引用N,这个N指向类或接口

如果N表示一个非数组类型
将代表N的全限定类名交给类的C类加载器加载,而在加载的过程中,元数据,字节码等阶段又可能
触发新的相关类的加载动作,只有这些连锁动作中任意动作抛出异常。则解析失败
如果表示一个数组类型例如 [Ljava/lang/Integer
首先按照第一步加载数组的类型,然后由JVM生成一个代表该数组维度和元素类型的对象
最后验证权限
最后确认类D是否对类C具有访问权限,如果没有,则抛出异常
  • 字段的解析

      首先根据字段表中的class_index加载该字段的所属类C
      如果类C加载成功,则在C中茶盅是否有与该字段简单名称和描述符相等的字段,如果有返回
      如果没有,从类C的父接口从上到下的递归寻找
      如果还没有,且该C不是java.lang.Object,则在父类中从上到下的递归查找
      最后如果找到,则检查访问权限
      如果未找到,则抛出异常
    
  • 类方法的解析

      首先根据方法表中的class_index加载该方法所属类型C
      加载成功后。因为这是一个类方法,所以先检查C是接口还是类,如果是接口抛出异常
      通过后,则检查类C中是否有与该方法简单名称和符号引用都相同的方法,如果有则返回
      否则,在父类中递归查找,找到则返回
      否则,在类C实现的接口中查找,如果找到则说明这是一个抽象类直接抛出异常
      否则,查找失败。
      最后如果查找成功,则老规矩,进行权限验证
    
  • 接口方法的解析

      接口方法的解析和类方法的解析相类似
      第一步,加载class_index指向的类型C
      检查C是否是一个接口,如果不是,抛出异常
      如果是的话,在接口中查找该方法简单名称和符号引用都相同的方法
      如果未找到,在父接口中递归查找。
      如果还是未找到,则失败抛出异常
    

tip:接口中的方法都是public static 的所以不用验证权限


初始化阶段

初始化阶段是类加载的最后一个阶段,该阶段对类中的变量及其他资源进行初始化

在准备阶段,已经对类变量进行一次赋零值的操作,但是在初始化阶段才是真正的按照从程序员的
编码对类的变狼进行赋值。 
在初始化阶段会执行类构造器<cinit>,类构造器并不是程序员书写的,而是javac收集类变狼赋值
语句和static代码块而生成的。而且静态代码块只能访问到定义在静态代码块之前的变量。
<cinit>对于类和接口不是必须的,对于类来说,如果没有静态代码块和类变量赋值语句,就不会
有类构造器;对于接口来说,因为不支持static代码块,所以如果没有变量赋值,也不会有

tip: 因为在子类初始化的时候,如果父类未初始化,那么先初始化父类。所以第一个执行的类构造器
一定是java.lang.Object的;但是接口不同,接口只有父接口中定义的变量被使用的的时候,
才会被初始化。

线程安全

java保证,<cinit>的线程安全,多个线程初始化一个类,则cinit只会被执行一次。所以我们说
在多线程环境下,static变量的赋值和static代码块中的语句都是线程安全的。

类的卸载

类的卸载需要满足以下条件

  • 该类的所有实例化对象都已经被GC

  • 该类的类加载器语已经被GC

  • 该类没有在其他任何地方存在引用

    需要注意的时候,即使满足了类的卸载条件,类也不一定会被卸载。由java自带的类加载器加载
    的类是不会被卸载的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值