《深入理解Java虚拟机》的阅读笔记。
类加载就是把.class文件放入内存,并进行校验等过程得到Java类型。
Java的特点是运行时按需加载(动态加载),而非编译时全部加载。动态加载和动态连接共同构成Java动态扩展的特性(运行时决定类型、装载、使用)。
类的生命周期有:加载–>验证–>准备–>解析–>初始化–>使用–>卸载。
前五个属于类加载(其中解析开始的时间并不确定,可以是在初始化之后),2、3、4属于连接。
加载
类的全限定名(包名.类名)–>二进制字节流–>运行时数据结构(方法区内)–>class对象(内存中)
全限定名
简单说是完整的包名.类名
官方文档说明:
- 基本类型:基本类型
- 有名字的包A,并且不是另一个有名字的包的子包:简单名
- 有名字的包A,并且是另一个有名字的包B的子包:B的全限定名.A的简单名
- 顶层类/接口A,声明在没有名字的包下:简单名
- 顶层类/接口A,声明在有名字的包下:包的全限定名.A的简单名
成员类,成员接口,数组可能有全限定名:
- 成员类/接口M,外层类/接口是C,当且仅当C有全限定名的时候M才会有全限定名,是C的全限定名.M简单名
- 数组M有全限定名当且仅当它的元素类型C有全限定名,其为
C全限定名[]
.
二进制字节流
可以从多种地方获取:
- zip包:jar、ear、war等
- class文件
- 网络
- 动态
非数组类的可控性强,通过自定义类加载器控制获取方式;
数组类C的创建:
- 如果C的组件类型B是引用类型,递归加载B,C在B的加载器的类名称空间被标识。
- 如果B是基本类型,B的类加载器为Bootstrap。
类加载器
设置如何获取需要的类
类的唯一性判断:
- 类加载器(类名称空间)
- 类本身
双亲委派模型
好处:Java类随类加载器有优先级,不会有多个基础类
父加载器结构:自定义–>Application(用户类路径,默认的加载器)–>Extension(
实现:
classloader类a中的loadClass(name)方法:
- name是否被加载过?有,直接返回其加载器,没有,goto 2
- a的父加载器是否是null?是,则由boostrap加载,否则由a的父加载器加载。
- 加载正常,返回加载器;加载异常,由自己加载
findClass(name)
破坏:
- 双亲委派模型出现之前,是重写
loadClass
,之后是重写findClass
- 基础类需要调用户类代码:用线程上下文类加载器
- 动态性:OSGi的自定义类加载机制
验证
检查class文件是否符合虚拟机要求,是否安全。虽然Java语言是安全的,但是类加载的二进制文件不一定是Java生成的Class文件,还可能是其他的。
非必要,如果之前验证过,可设置跳过
验证内容:
- 文件格式:魔数、版本号、是否支持常量类型、是否存在指向常量的索引且符合类型?UTF-8常量编码
- 元数据(语义):有父类、是否继承了不允许继承的类、非抽象类是否实现了所有方法、是否有与父矛盾(final、不合法重载)的字段或方法
- 字节码(语义、逻辑,方法安全):跳转、类型转换、操作栈
- 符号引用(发生在解析时的符号引用–>直接引用):全限定名是否能定位到类、类中有对应的方法或字段、是否可访问
准备
给类变量(static)在方法区分配内存
- 非final字段:设置默认初值
- static final字段:在编译时在ConstantValue已经有值,在准备时根据ConstantValue的值直接给字段赋值
解析
把常量池中的符号引用(字面量)转为直接引用。发生的时机不具体,但是16个用于操作符号引用的指令之前一定会完成解析。
直接引用可能是:
- 直接指向目标的指针、偏移量
- 间接定位到目标的句柄
多次解析:
- 非invokedynamic指令:可缓存(运行时常量池记录直接引用,标识为已解析状态),需要保证每次解析的状态一样(成功、相同异常)
- invokedynamic指令是执行到才解析。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!