关于jvm:JVM笔记-来教你类加载子系统

42次阅读

共计 2259 个字符,预计需要花费 6 分钟才能阅读完成。

类加载子系统

类文件首先须要通过类加载子系统,进行加载,进类信息等加载到运行时数据区,生成 Klass 的实例。

在类加载子系统中有以下 3 个阶段操作(狭义上的加载):

  • 加载阶段

    • Bootstrap ClassLoader:疏导类加载器,次要加载 JDK 外面的外围类
    • Extension ClassLoader:拓展类加载器
    • Application ClassLoader:利用加载器
  • 链接阶段

    • 验证
    • 链接
    • 解析
  • 初始化阶段


如果加载的时候失败了,则不会执行前面的链接等操作。

类加载子系统的作用:

  • 类加载器子系统能够从 本地文件 或者 网络 中加载 Class 文件,Class 文件结尾有特定标识“CAFEBABY”(魔数)。
  • 类加载器只负责将文件加载到运行时数据区,但 是否能够运行,是执行引擎管的
  • 加载的类信息寄存在办法区中,除了类信息以外,办法区还寄存了运行时产量池信息,可能 HIA 包含字符串字面量和数字常量(这部分常量是 Class 文件中常量池局部的内存映射)。

譬如反编译后,会产生常量信息,外面包含常量以及符号援用等:

类加载器 ClassLoader 的角色, 以上面的 People.class 为例:

通过类信息实例,能够通过 new 实例化对象,也能够通过 getClassLoader()获取类加载器,也能够通过实例 getClass()获取类信息实例。

  1. People.class 存在本地硬盘上,相当于一个模板,最终能够实例化出 n 个同一个类然而属性不同的实例。
  2. People.class 加载到 JVM 中,被称为 DNA 元数据模板,寄存在办法区,也就是类信息。类信息也是对象。
  3. 从.class 文件,到加载到 JVM 中,称为元数据模板,这个过程须要一个转换工具,这个工具就是类加载器(Class Loader)。

加载(Loading)

此处的加载,指的是类加载过程中的第一个阶段(环节),次要工作包含:

  • 1. 通过类的全限定名获取定义此类的二进制字节流。
  • 2. 将这个二进制字节流所代表的动态存储构造转化为办法区 (JDK7 以及之前叫永恒代,JDK8 之后成为元空间) 的运行时数据结构。
  • 3. 在内存中生成一个该类的 java.lang.Class 对象,作为办法区该类的各种数据的拜访入口,也就是类信息对象。

类的.class 文件起源形式包含以下:

  • 本地零碎间接加载
  • 网络传输获取
  • 从 zip 压缩包读取
  • 运行的时候计算生成,譬如动静代理技术
  • 由其余文件生成,譬如场景:JSP
  • 从加密文件中解密取得

链接

链接阶段又分为 3 个阶段:

  • 验证:

    • 目标是校验平安和法,确保 Class 文件的字节流中蕴含信息合乎以后虚拟机要求,保障加载的类的正确性,不会危害到虚拟机的平安。
    • 次要包含 4 种验证:

      • 文件格式验证(譬如文件结尾是 ”CAFEBABY”)
      • 元数据验证
      • 字节码验证
      • 符号援用验证
  • 筹备:

    • 为类变量(static)分配内存并且设置该变量的默认初始值,即零值
    • 不蕴含 final 润饰的 static,因为 final 在编译的时候曾经调配了,筹备阶段会显示初始化。
    • 不会为实例变量调配初始化,类变量会调配在办法区,然而实例变量是追随对象一起调配在 Java 堆外面(个别状况)
  • 解析:

    • 将常量池的符号援用转化成为间接援用的过程
    • 事实上,解析操作往往会随同 JVM 在执行完 初始化之后再执行
    • 符号援用就是一组符号来形容所援用的指标,《Java 虚拟机标准》的 Class 文件格式中,间接援用就是间接指向指标的指针,绝对偏移量或者一个间接定位到指标的句柄。
    • 解析这个阶段,次要是针对类或者接口,字段,类办法,接口办法,办法类型等等,对应的常量池中的 CONSTANT_Class_info,CONSTANT_Fieldred_info,CONSTANT_Methodref_info 等。

初始化

初始化,就是执行类的结构器 <clinit>() 的过程,留神 <clinit>() 是类的结构器,不是对象的。<clinit>()是初始化类的,就是把类装到 JVM 里的初始化,不是运行时对象的初始化。

<clinit>()这个办法不须要显式定义,而是 javac 编译器主动收集类中的所有变量的赋值动作,加上动态代码块,合并成的一个办法。

<clinit>()中代码的程序和咱们在类文件写的程序统一。

执行子类的 <clinit>() 办法之前,JVM 会保障先执行其父类的<clinit>(),默认父类是Object

仔细观察下面的代码,会发现,final 的属性,即便是 static 润饰的,在 <clinit>() 外面都不会存在,这是为什么呢?

这是因为 final 润饰的是常量,常量不会在初始化的时候执行赋值!!! 常量在编译的时候曾经调配了,筹备阶段会显示初始化。

如果咱们将 final 去掉,就能够发现, 去掉 final 润饰,字节码就会加上该字段的赋值:(上面的 ldc 是指常量池的意思,从常量池编号为 #6 的中央,加载该常量)

虚拟机在初始化的时候,曾经保障了类的 <clinit>() 办法,即便在多线程的环境下,也只会执行一次,其底层的逻辑就是默认同步加锁了。

【作者简介】
秦怀,公众号【秦怀杂货店】作者,技术之路不在一时,山高水长,纵使迟缓,驰而不息。集体写作方向:Java 源码解析,JDBC,Mybatis,Spring,redis,分布式,剑指 Offer,LeetCode 等,认真写好每一篇文章,不喜爱题目党,不喜爱花里胡哨,大多写系列文章,不能保障我写的都完全正确,然而我保障所写的均通过实际或者查找材料。脱漏或者谬误之处,还望斧正。

2020 年我写了什么?

开源刷题笔记

素日工夫贵重,只能应用早晨以及周末工夫学习写作,关注我,咱们一起成长吧~

正文完
 0