您的位置:首页 > 编程语言 > Java开发

深入Java虚拟机读书笔记之执行子系统

2016-03-13 19:59 253 查看

深入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初始化函数内,因此是在初始化时赋值的。

成员变量:会随对象在堆内创建的时候,赋默认值,如果我们不给值的话

方法变量和静态块内的变量都是必须在声明的时候赋值的,不然在后面的语句中如果使用,编译器编译无法通过。

参考资料

http://blog.csdn.net/column/details/java-vm.html

https://m.aliyun.com/yunqi/articles/2358#index_section
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息