《深入理解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(/lib/ext)–>Boostrap(/lib下的,C++实现,用null判断)。父加载器的实现并非用继承,而是用组合。

实现:
classloader类a中的loadClass(name)方法:

  1. name是否被加载过?有,直接返回其加载器,没有,goto 2
  2. a的父加载器是否是null?是,则由boostrap加载,否则由a的父加载器加载。
  3. 加载正常,返回加载器;加载异常,由自己加载findClass(name)

破坏:

  • 双亲委派模型出现之前,是重写loadClass,之后是重写findClass
  • 基础类需要调用户类代码:用线程上下文类加载器
  • 动态性:OSGi的自定义类加载机制

验证

检查class文件是否符合虚拟机要求,是否安全。虽然Java语言是安全的,但是类加载的二进制文件不一定是Java生成的Class文件,还可能是其他的。

非必要,如果之前验证过,可设置跳过

验证内容:

  • 文件格式:魔数、版本号、是否支持常量类型、是否存在指向常量的索引且符合类型?UTF-8常量编码
  • 元数据(语义):有父类、是否继承了不允许继承的类、非抽象类是否实现了所有方法、是否有与父矛盾(final、不合法重载)的字段或方法
  • 字节码(语义、逻辑,方法安全):跳转、类型转换、操作栈
  • 符号引用(发生在解析时的符号引用–>直接引用):全限定名是否能定位到类、类中有对应的方法或字段、是否可访问

准备

给类变量(static)在方法区分配内存

  • 非final字段:设置默认初值
  • static final字段:在编译时在ConstantValue已经有值,在准备时根据ConstantValue的值直接给字段赋值

解析

把常量池中的符号引用(字面量)转为直接引用。发生的时机不具体,但是16个用于操作符号引用的指令之前一定会完成解析。
直接引用可能是:

  • 直接指向目标的指针、偏移量
  • 间接定位到目标的句柄

多次解析:

  • 非invokedynamic指令:可缓存(运行时常量池记录直接引用,标识为已解析状态),需要保证每次解析的状态一样(成功、相同异常)
  • invokedynamic指令是执行到才解析。


JVM Java

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!