执行引擎(二):虚拟机类加载机制

  • 虚拟机类加载机制:
    • 虚拟机把 描述类数据 从Class文件加载到内存,
    • 对数据进行校验,转换解析,初始化,
    • 最终形成可被虚拟机直接使用的Java类型。
    • 注类型的加载,链接,初始化过程 在程序运行期间完成的,虽然导致类加载时增加性能开销(忽略不计),但会为Java应用程序提供高度的灵活性(java语言动态拓展依赖:运行期动态加载和动态链接)

执行引擎

  • 物理机
    • 概述
      • 执行引擎建立在处理器,硬件,指令集,操作系统层面上
  • 虚拟机
    • 概述
      • 执行引擎由自己实现,可自行制定指令集与执行引擎的结构体系,并且能够直接执行哪些不被硬件直接支持的指令集格式
  • java虚拟机执行引擎:
    • 概述
      • 输入字节码文件,处理过程是字节码解析的等效过程,输出执行结果
      • 在不同的虚拟机实现里,执行引擎在执行java代码的时候
  • 解释执行:通过解释器执行
  • 编译执行:通过即使编译器产生本地代码执行

  • 类加载时机
    • 初始化
  • 类加载过程
    • 加载
      • 实现
        • 1.通过 类全限定名 获取 该类二进制字节流
        • 2.字节流静态存储结构 => 方法区运行时数据
        • 3.内存生成该类的java.lang.Class对象作为方法区这个类的各种数据的访问入口。
          • (Hotstop虚拟机将Class对象存放到方法区中)
        • 注:虚拟机规范的这3点要求并不及具体,因此虚拟机实现与具体应用的灵活度很大(如没有指明流的获取地址)
          • 开发人员可以通过自定义的类加载器去控制字节流的获取方式
          • 即:重写一个类加载器的loadClass()方法)
        • 注:数组类本身不通过类加载器创建,由java虚拟机直接创建的,元素类型最终要靠类加载器去创建。
      • 遵循:
        • 数组元素类型类加载器的类空间名称标识;
        • 1.引用类型,
          • 递归采用加载过程去加载这个组件类型
          • 加载该组件类型的类加载器的 类名称 空间上标识 数组
        • 2.非引用类型,数组标记 与 引导类加载器 关联
        • 可见性:
          • 数组类的可见性与他的元素类型可见性一致,
          • 非引用类型,默认public
      • 类加载器
        • 概述
          • 虚拟机团队把类加载阶段中"通过一个类的全限定名来获取描述此类的二进制字节流"放到java虚拟机外部来实现,
          • 以便让 程序 决定 所需类如何去获取 
      • 类与类加载器
        • 启动类加载器
          • 负责将存放在<JAVA_HOME>\lib目录中的
          • 或者被-Xbootclasspath参数所指定的路径中
          • 并且是虚拟机识别的(仅按照文件名识别如rt.jar,名字不符合的类库即使放在lib中也不会被加载)类库加载到虚拟机内存中。
          • 无法被java程序直接引用
          • 编写自定义加载器时,如需把加载请求委派给引导类加载器,直接使用null代替即可。
        • 扩展类加载器
          • 该加载器由sun.misc.Launcher$ExtClassLoader实现,
          • 负责加载<JAVA_HOME>\lib\ext目录中的,
          • 或者被java.ext.dirs系统变量指定的路径中的所有类库。
          • 可直接使用
        • 应用程序类加载器
          • 这个类加载器由sun.misc.Launcher$App-ClassLoder实现。
          • 默认系统类加载器(由于这个类加载器是ClassLoader中getSyetemClassLoader()方法的返回值)。
          • 负责加载用户类路径(ClassPath)上所指定的类库,
          • 可直接使用
        • 默认的类加载器
          • 概述
            • 任何一个类:都需要由 加载它类加载器(拥有独立的类名称空间)和 这个类的本身 确立java虚拟机中的唯一性
            • 比较两个类是否相等前提:同一个类加载器加载
        • 从java虚拟机的角度:
          • 启动类加载器:
            • C++语言实现,属于虚拟机的一部分
          • 所有其他类加载器:
            • java语言实现,独立于虚拟机外部,继承自抽象类java.lang.ClassLoader
      • 双亲委派模型
        • 工作过程
          • 类的加载器收到了 类加载 请求:
            • 1.把该请求委派给父类加载器去完成(所有的加载请求 最终都应传送到 启动类加载器 中)
            • 2.如果父加载器无法完成(搜索范围中没有找到所需的类),子加载器才会尝试加载。
        • 为什么使用?
          • 一致性
            • java类加载具备 优先级层次 关系。保证java程序的稳定运作
            • 如:Object类,最终委派给处于模型最顶端的启动类加载器进行加载,

            • 因此Object类在程序的各种类加载器环境中都是同一个类。

            •  

              相反,如果由各个类加载器自行加载的话,
            • 那么就会有许多个不同的Object类。
        • 怎么使用?
          • 1.请求类是否 已被加载。
          • 2.无调用 父类加载器的loadClass()方法
          • 3.如父类加载器为空 则使用默认启动类加载器作为父类加载器,
          • 如果父类加载失败,则抛出ClassNotFoundException异常,在调用自己的findClass()方法进行加载。
        • 特例:只要有足够意义和理由,突破已有的原则就可以认为是一种创新
          • JDK1.2之前
          • 基础类回调用户代码
          • OSGI:
            • 概述
              • 在OSGI环境下,类加载器不再是双亲委派模型,而是进一步发展成更加复杂的网络结构。
            • 搜索顺序
              • 1.java.*开头的类委派给父类加载器加载
              • 2.否则,委派列表名单类 委派给 父类加载器加载
              • 3.否则,将import列表类 委派给 Export类的Bundle的类加载器加载
              • 4.否则,查找Bundle的ClassPath,本身类加载器进行加载
              • 5.否则,查找类是否在本身Fragment Bundle中,
                • 如果在,则委派给Fragment Bundle的类加载器进行加载
              • 6.否则,查找DYnamic Import列表的Bundle,委派给对应的Bundle的类加载器加载
              • 7.否则,查找失败
        • 注:双亲委派模型要求:
          • 除了顶级启动类加载器外,其余的类加载器 都应有父类加载器。一般使用组合关系复用父加载器的代码。
          • 双亲委派模型并不是强制性的约束模型,而是java设计者推荐给开发者的一种类加载实现方式。
    • 链接
      • 概述
        • 即 将常量池 符号引用 => 直接引用
        • 符号引用:
          • 以一组符号来 描述 所引用的目标(可以是任何形式的字面量)无歧义的定位到目标即可。虚拟机实现内存布局无关。
          • 符号引用的字面量形式明确定义在java虚拟机规范的Class文件中
        • 直接引用:
          • 直接引用可以是 直接指向目标的指针,相对偏移量或间接定位到目标的句柄。与虚拟机实现的内存布局相关,
          • 同一个符号引用 不同的虚拟机实例 翻译 的直接引用 一般不会相同,
          • 如有直接引用,那引用的已存在内存中。
      • 虚拟机规范中未规定解析阶段发生时间,要求在执行
        • anewarry.
        • checkast,
        • getfield,
        • instanceof,
        • invokeinterface,
        • invokespecial,
        • invokestatic,
        • invokevirtual.
        • ldc,
        • ldc_w,
        • multianewarry,
        • new,
        • putfield,
        • putstatic
      • 16个(用于操作符号引用)字节码指令之前
        • 除invokedynamic指令(动态调用点限定符:必须等到程序实际运行到这条指令的时候,解析动作才能进行)外,
        • 虚拟机实现可对第一次解析结果缓存(运行时常量池中记录直接引用,标识已解析状态)

    • 验证
      • 文件格式验证
        • 概述
          • 验证字节流 是否 符合Class文件的规范
          • 可被 当前版本的虚拟机 处理(魔术,主次版本号,常量池中常量类型)
          • 保证输入的字节流能正确的 解析并 存储 到方法区之内,符合描述一个java类型信息的要求。
      • 元数据验证
        • 概述
          • 对字节码描述的信息进行语义分析,对类的元数据信息进行语义检验,
          • 符合java语言规范的元数据信息。
      • 字节码验证
        • 概述
          • 验证通过数据流和控制流的分析(程序 校验逻辑 准确性低)对类的方法体进行校验
          • 确定程序语义合法,符合逻辑。
          • 保证运行该方法虚拟机安全
            • JDK1.6之后javac编译器 , java虚拟机 方法体的Code属性的 属性表中 增加StackMapTable的属性
              • 字节码验证:类型推导 => 类型检查
                • 描述了方法体 基本块开始时 本地变量表, 操作栈应有状态
                • 在字节码验证期间,只需检查StackMapTable属性中的记录是否合法即可。
              • 使用
                • -XX:-UseSplitVerifier关闭。
                • -XX:+FailOverToOldVerifier:验证失败的时候退回到类型推到方式。
                  • 注:JKD1.7之后(主版本号大于50的Class文件):使用类型检查来完成数据流分析检验是唯一的选择。
      • 符号引用验证
        • 概述
          • 发生时间:虚拟机将符号引用转换 => 直接引用 时(转换动作在链接的第三阶段,解析阶段发生)。
          • 对类自身之外的信息(常量池中的各种符号引用) 进行匹配性的校验
          • 目的:确保解析动作能正常执行。
            • 无法通过符号引用验证,则抛出
              • java.lang.IncompatibleClassChangeError异常的子类:
              • java.lang.IllegalAccessError
              • java.long.NoSuchFieldError
              • java.lang.NoSuchMethodError
              • 使用:-Xverify:none关闭大部分的类验证措施。
    • 准备
      • 变量分配内存(将在方法区中进行分配
      • 设置类变量初始值的阶段,
    • 解析
      • 类或接口的解析
        • 符号引用 (N)=> 直接引用(C),类(D):
          • C:数组类型,
            • 把 代表N的全限定名 传递 给D的类加载器 去加载C
            • 在加载过程中:元数据的验证,字节码的验证 可触发 其它相关类的加载动作
            • 如出现异常,解析过程失败。
          • C:数组类型数组的  数组元素:对象
            • 将按1的规则加载数组类型的元素。
            • 随后生成一个代表此数组维度和元素的数组对象
          • 符号引用的验证,
            • 确认D是否具备对C的访问权限,
              • 如不具备,抛出java.lang.IllegalAccessError异常
      • 字段解析
        • 对字段表内class_index项索引的 CONSTANT_Class_info 符号引用 进行解析
          • 目的:查询该字段所属的类或接口C(即字段所属的类或接口的符号引用)
            • 1.查找类本身
              • 如C包含 简单名称,字段描述符 匹配
                • 返回该字段直接引用
            • 2.查找类的父接口
              • 如C实现了接口,按继承关系从下往上 递归搜索 各个接口
              • 如包含 简单名称,字段描述符 匹配
                • 返回该字段直接引用
            • 3.查找类的父类
              • 如C不是java.lang.Object,按继承关系 从下往上 递归搜索 父类,
              • 如包含 简单名称,字段描述符 匹配
                • 返回该字段直接引用
            • 4.查找失败
              • 抛出java.lang.NoSuchFeldError异常
              • 注:如成功返回,将对该字段进行权限验证
                • 无权限则抛java.lang.IllegalAccessError异常。
          • 在编译器中:
            • 如 同名字段同时出现在接口和父类中或同时在自己或父类中的多个和接口中出现,则编译器拒绝编译。
      • 类方法解析
        • 对类方法表内class_index项索引的方法所属的类或接口的符号引用进行解析
          • 目的:查询到该字段所属的类或接口C(即类方法所属的类或接口的符号引用)
            • 1.类方法
              • 如类方法中class_index中索引的C是接口
                • 则抛java.lang.IncompatibleClassChangeError异常
            • 2..查找类本身
              • 在类C中查找简单名称和描述符都与目标相匹配的方法,
                • 如成立则返回该方法直接引用 结束
            • 3.查找类的父类
              • C父类 递归查找  简单名称 描述符 匹配的方法,
                • 如成立则返回该方法直接引用 结束
            • 4.查找类的父接口
              • C实现接口,父接口 递归查找 简单名称,描述符 匹配的方法
                • 如成立则返回该方法直接引用 结束
            • 5.查找失败
              • 抛出java.lang.NoSuchMethodError异常
            • 注:如果查找成功,
              • 对该方法进行权限验证
              • 无权限 将抛java.lang.IllegalAccessError异常。
      • 接口方法解析
        • 对接口方法表内class_index项索引的方法所属的类或接口的符号引用进行解析
          • 目的:查询到该字段所属的类或接口C(即接口方法所属的类或接口的符号引用)
            • 1.接口方法
              • 如 接口方法 发现class_index中索引的 C是个类,
                • 则抛java.lang.IncompatibleClassChangeError异常。
            • 2..查找接口本身
              • 接口C 查找 简单名称,描述符都 匹配方法,
                • 如成立则返回该方法直接引用,结束
            • 3.查找接口的父接口
              • C父接口 递归查找 简单名称 描述符 匹配方法,
                • 如成立则返回该方法直接引用,结束
            • 4.查找失败
              • 抛出java.lang.NoSuchMethodError异常
            • 注:接口中所有方法默认都是public的,
              • 所以不会抛出java.lang.IllegalAccessError异常。
  • 初始化
    • 概述:
      • 执行类构造器<clinit>()方法的过程。
      • 除加载阶段(应用程序可通过自定义加载器参与)之外.其余动作由虚拟机主导可控
      • 初始化阶段,开始真正执行类中定义的java程序代码(或者字节码)。
    • 1.<clinit>()方法是 由编译器自动收集类中所有类变量赋值动作 静态语句块(static{})中语句合并产生的。
      • 收集顺序 由语句在源文件顺序 决定
    • 2.<clinit>()方法,类的构造函数<init>()区别:
      • 无显示调用父类构造器,
      • 子类的<clinit>()之前 父类的<clinit>()方法已执行完毕。
        • 因此虚拟机中第一个被执行的<clinit>()方法的类:java.lang.Object
        • 父类中静态语句块要优先于子类的变量赋值操作。
    • 说明:
      • <clinit>()方法对于 类或 接口 非必须
      • 如类中无静态语句块,无变量赋值操作,
        • 编译器可不生成<clinit>()方法。
      • 接口不能使用静态语句块,但仍有变量初始化赋值 操作,
        • 接口会生成<clinit>()方法。
      • 当父接口中定义的变量使用时,父接口才会初始化,
      • 接口实现类初始化时 不会执行 接口的<clinit>()方法 
      • 同一个类加载器下,一个类型只会初始化一次。
    • 虚拟机保证一个类的<clinit>()方法在并发环境中正确同步
      • 如多个线程同时初始化一个类,那么只有一个线程会执行这个类的<clinit>()方法,
      • 其他的线程都需要阻塞到活动线程执行 <clinit>()方法完毕,
        • 如类的<clinit>()方法中有耗时很长,可能造成多个进程阻塞。

转载于:https://www.cnblogs.com/lllllht/p/9177902.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值