深入Java虚拟机读书笔记之执行子系统
class文件结构
这种大致思想就是通过某种规范的形式记录下类的信息,继承、接口、类名,异常、属性、方法等。
该记录的基本单位是无符号数和表,其实表也是无符号数构成的。本质上这个class文件就是一张大表。
本图来自:http://www.tucaobj.com/note/java/201410091515194083.jhtml
无论是对于无符号数还是表,对于同一类型但是数量不定的多个数据时,前面必须加个容器计数器(常量表、字段表、方发表、属性表),这样才能清晰的描述哪些字段到底表示什么意思。
然后jvm加载的时候就能按照此规范将字节码文件加载到jvm内存的方法区(静态区)。
常量池
常量池是常量这同一种类型,因此必须前面加上一个容器计数器。虽说是同一种类型,但是如果细分,常量类型共有十一种,其共同特点就是他们的第一位都是一个u1的标志位。
这十一种总是属于这两类:字面量和符号引用。
符号引用会有一个name_index指向字面量。
可以用notepad++(必须安装个插件)打开class文件看看
也可以直接用javap -verbose 查看常量表
code表
很重要
虚拟机类加载机制
1.类加载时机
类的声明周期
加载->验证->准备->解析->初始化->使用->卸载顺序讨论
其中验证、准备、解析属于链接的过程,但是对于连接过程中的解析比较特殊,他可以在初始化完成之后开始
因此在初始化之后在解析,是动态绑定实现的理论基础。加载时机
类加载的第一个阶段加载规范并没有约束,由虚拟机具体实现决定。
但是对于初始化的时机却是有着详细规定的。
2.加载过程
加载过程:加载–> 验证–>准备–> 解析–> 初始化
1. 加载
在java堆中会生成一个代表这个类的class对象,作为方法区这些数据的访问入口
2. 准备
为类变量分配内存并设置类变量的默认值。
如果是实例变量自然是动态运行的时候随对象创建生成在堆中赋值,但是如果是静态常量final,因为其不变性,编译器完全可以在编译阶段就直接对其初赋值。
比如 public static final int value = 33;
- 解析
用直接引用替换符号引用的过程 - 初始化
前面的过程都是虚拟机主导的,从这里开始执行类构造器clinit方法,可以由程序开发人员控制了。
3.类加载器
类加载器和类
通过一个类的全限定名来获取描述此类的二进制字节流放到java虚拟机外部实现,这就是类加载器的来源。
由同一个类加载器加载的同一份class文件在内存中的类才是相等的,换言之,加载它的类加载器和这个类本身一同确立其在JVM中的唯一性。相等的三种判断方式:
- Class对象的equlas方法、isAssignableFrom方法、isInstance方法
双亲委派模型
顶层是 bootstrap classLoader–>Extension ClassLoader–>application classLoader
分别加载lib 、ext、我们的程序文件,如果我们不自定义,application是默认的应用程序类文件加载器对于jvm而言,世界上的classLoader只有两种,bootstrap classLoade和非bootstrap。bootstrap是jvm内部的用c/c++写的,其他的大都使用java书写的。
application classLoader是classLoader中的getSystemClassLoader方法的返回值,一般也被成为系统类加载器。负责加载用户类路径上所指定的类库。
双亲委派模型:首先让父类加载,如果父类加载失败,继续往上请求,顶层失败,逆序反馈,最后自己加载。
我们的加载器首先让自己的父类加载,复用父类的方法,并不是通过继承来实现的,而是通过组合来实现的。
双亲模型的意义何在?
答:无论哪一个类加载这个类,最终都会被委派到引导类加载器去完成它的加载,因此Object类在程序中的各种类加载器环境中都是一个类。这样做也保证安全性,因为如果有人想恶意置入代码,类加载器的代码就避免了这种情况的发生。破坏双亲委派
虚拟机字节码执行引擎
这部分内容和class字节码的code属性表息息相关
栈帧结构
- 局部变量表在编译期间就算好最大容量,这个并不是简单的将参数,方法内变量的容量相加的,而是考虑到方法内作用域的不同,将slot复用。
- 方法执行的时候,参数传递:如果不是类的静态方法,即实例方法,在调用时索引号位0的slot是当前对象的引用this。方法参数然后依次排列。
操作数栈:方法开始执行的时候栈为空,执行过程中不断出栈和入栈。比如方法调用的时候就是通过操作数栈来进行参数传递。
本方法的操作数栈帧的内容可以作为被调用方法的参数(被调用方法的局部变量区和调用者的操作数栈共享数据)动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。能通过该引用找到常量池地址,然后将直接引用替换符号引用。- 返回地址
方法调用
基于栈的字节码解释执行引擎
书上有图:一图胜千言
其他问题
- 类变量:在准备阶段赋默认值,对于public static int a = 12;这种在编译期间 a = 12会被编译进 clinit初始化函数内,因此是在初始化时赋值的。
- 成员变量:会随对象在堆内创建的时候,赋默认值,如果我们不给值的话
- 方法变量和静态块内的变量都是必须在声明的时候赋值的,不然在后面的语句中如果使用,编译器编译无法通过。