Java虚拟机学习笔记(四)——类文件与类加载

一:JVM的特性

1. 平台无关性

  • 程序需要被翻译器翻译成0和1构成的二进制格式——机器码,才能被计算机执行
  • 虚拟机在不同的操作系统上,都可以载入和执行同一种平台无关的字节码(类文件),实现“一次编写,到处执行”,将字节码翻译成机器码

2. 语言无关性

多种语言可以在Java虚拟机上运行,通过不同的编译器,将程序的代码翻译成字节码的(类文件),再被虚拟机翻译成机器码

二:类加载机制

虚拟机把描述类的数据从Class文件加载到内存,对数据进行校验、转换、解析、初始化,最终形成可以直接被虚拟机使用的Java类型。

  1. 加载
  2. 连接
    1. 验证
    2. 准备
    3. 解析(可能在初始化之后)
  3. 初始化
  4. 使用
  5. 卸载

1. 类加载过程

(1)类的加载
  1. 通过类的全限定名获取定义此类的二进制字节流(可以有多种方式)
  2. 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3. 在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区数据的访问入口
(2)类的验证
  • 文件格式验证:验证字节流是否符合Class文件格式的规范(常量、版本号等),并且能被当前版本的虚拟机处理,进入内存的方法区中存储
  • 元数据验证:对字节码描述信息的语义(类、接口等)分析,保证其符合Java语言的规范
  • 字节码验证:对类的方法体进行数据流和控制流分析,保证类不会危害虚拟机
  • 符合引用验证:在解析阶段进行(把符合引用转化为直接引用),对类自身以外的信息进行匹配性的校验,确保解析动作能正常执行
(3)类的准备

正式为类变量分配内存并设置类变量初始值(变量为0,常量为目标值)的阶段,在方法区进行

(4)类的解析

将常量池内的符号引用替换为直接引用的过程

  • 符号引用:以一组符号表示引用的目标。符号可以是任何字面量,与虚拟机实现的内存布局无关。引用目标不一定加载到内存中
  • 直接引用:直接指向目标的指针、相对偏移量或间接定位目标的句柄。与虚拟机实现的内存布局相关,引用的目标一定在内存中
解析时机
  • 类被类加载器加载时进行解析
  • 符号引用将被使用时进行解析
解析缓存

在运行时常量池中记录直接引用,并把常量标识为已解析状态,避免重复解析

类或接口解析

当前代码所处的类为B1,符号A解析为一个类或接口B2的一个引用的话

  • 加载类B2
    • B2不是一个数组类型:虚拟机会把代表A的全限定名传递给B1的类加载器去加载这个 类B2
    • B2是一个数组类型,且数组元素为对象:虚拟机会先加载数组元素的类,再生成一个代表此数组维度和元素的数组对象
  • 符号引用验证,确认B2对B1有访问权限
字段解析
  • 对字段表内的class_index项中索引的CONSTANT_Class_info符号引用进行解析,也就是解析字段A所属的类或接口的符号引用,用B1表示
    • 如果B1本身包含了简单名称和字段描述符都匹配的字段,则直接返回此字段的引用
    • 如果B1实现了接口,则按照继承关系从上往下递归搜索各个接口和父接口。如果接口包含了简单名称和字段描述符都匹配的字段,则直接返回此字段的引用
    • 如果B1不是Object类的话,按照继承关系从上往下递归搜索父类,如果父类中包含了简单名称和字段描述符都匹配的字段,则直接返回此字段的引用
  • 对字段的引用进行权限验证,确认对该字段有访问权限
类方法解析
  • 对类方法表内的class_index项中索引的CONSTANT_Class_info符号引用进行解析,也就是解析类方法A所属的类或接口的符号引用,用B1表示
    • 如果B1是一个接口,解析失败
    • 如果B1本身包含了简单名称和描述符都匹配的方法,则直接返回此方法的引用
    • 如果B1不是Object类的话,按照继承关系从上往下递归搜索父类,如果父类本身包含了简单名称和描述符都匹配的方法,则直接返回此方法的引用
    • 如果B1实现了接口,则按照继承关系从上往下递归搜索各个接口和父接口。如果接口包含了简单名称和描述符都匹配的字段,则说明该方法是抽象方法,解析失败
  • 对方法的引用进行权限验证,确认对该方法有访问权限
接口方法解析
  • 对接口方法表内的class_index项中索引的CONSTANT_Class_info符号引用进行解析,也就是解析接口方法A所属的类或接口的符号引用,用B1表示
    • 如果B1是一个类,解析失败
    • 如果B1本身包含了简单名称和描述符都匹配的方法,则直接返回此方法的引用
    • 按照继承关系递归搜索父接口,直到Object类。如果接口包含了简单名称和描述符都匹配的字段,则说明该方法是抽象方法,解析失败
  • 不存在访问权限的问题
(5)类的初始化

真正执行程序的字节码,执行类构造器 < client >() 方法

< client >() 方法
  • 由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生
  • 先执行父类的< client >() 方法,再执行子类的< client >() 方法
  • 子接口不需要先执行父接口的< client >() 方法,当父接口定义的变量被使用时才执行
  • 接口的实现类不需要先执行接口的< client >() 方法
初始化时机

主动使用类:

  • 遇到字节码指令:new、getstatic、putstatic、invokestatic
    • new一个新对象实例
    • 读取或设置static字段
    • 调用static方法
  • 使用java.lang.reflect的反射方法对类进行调用
  • 如果初始化子类,需要先初始化父类(接口则不用)
  • 初始化Main方法的主类

被动使用类:

  • 通过子类引用父类的静态字段,不会导致子类的初始化
  • 通过数组定义来引用类,不会导致类的初始化
  • 常量在编译阶段会被存入常量池,不会导致类的初始化

2. 类加载器

通过一个类的全限定名来描述此类的二进制字节流,类和类加载器唯一确定一个类

(1)双亲委派模型
类加载器的种类

通过组合表示父子关系,而不是继承

  • 启动类加载器 Bootstrap ClassLoader:虚拟机自身的一部分。加载存放在 JRE 的 lib 目录下 jar 包中的类 ,由虚拟机参数 -Xbootclasspath 指定的类
  • 其他类加载器:虚拟机之外
    • 扩展类加载器 Extension ClassLoader:继承启动类加载器,加载存放在 JRE 的 lib/ext 目录下 jar 包中的类,由系统变量 java.ext.dirs 指定的类
    • 应用程序加载器 Application ClassLoader / 系统类加载器:继承扩展类加载器,加载虚拟机参数 -cp/-classpath指定的类,系统变量 java.class.path指定的类,环境变量 CLASSPATH指定的类
    • 自定义类加载器:继承应用程序加载器
工作过程
  1. 类加载器收到类加载的请求,委派给父加载器
  2. 如果父加载器反馈其无法完成加载请求(找不到所需的类),类加载器再尝试加载
(2)破坏双亲委派模型
  • 由用户重写loadClass()方法改为重写findClass()方法
  • 基础类需要调用用户类:父类加载器通过上下文类加载器,请求子类加载器完成类加载动作(JNDI、JDBC、JCE、JAXB、JBI)
  • 代码热替换、模块热部署:每个模块有属于自己的类加载器,发展为网状模型
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值