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

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

class文件结构

这种大致思想就是通过某种规范的形式记录下类的信息,继承、接口、类名,异常、属性、方法等。
该记录的基本单位是无符号数和表,其实表也是无符号数构成的。本质上这个class文件就是一张大表。
class文件字节码结构组织示意图
本图来自:http://www.tucaobj.com/note/java/201410091515194083.jhtml
无论是对于无符号数还是表,对于同一类型但是数量不定的多个数据时,前面必须加个容器计数器(常量表、字段表、方发表、属性表),这样才能清晰的描述哪些字段到底表示什么意思。
然后jvm加载的时候就能按照此规范将字节码文件加载到jvm内存的方法区(静态区)。

常量池

常量池是常量这同一种类型,因此必须前面加上一个容器计数器。虽说是同一种类型,但是如果细分,常量类型共有十一种,其共同特点就是他们的第一位都是一个u1的标志位。
这十一种总是属于这两类:字面量和符号引用。
符号引用会有一个name_index指向字面量。
常量池

可以用notepad++(必须安装个插件)打开class文件看看
也可以直接用javap -verbose 查看常量表

code表

很重要

虚拟机类加载机制

1.类加载时机

  1. 类的声明周期
    加载->验证->准备->解析->初始化->使用->卸载

  2. 顺序讨论
    其中验证、准备、解析属于链接的过程,但是对于连接过程中的解析比较特殊,他可以在初始化完成之后开始
    因此在初始化之后在解析,是动态绑定实现的理论基础。

  3. 加载时机
    类加载的第一个阶段加载规范并没有约束,由虚拟机具体实现决定。
    但是对于初始化的时机却是有着详细规定的。

2.加载过程

加载过程:加载–> 验证–>准备–> 解析–> 初始化
1. 加载
在java堆中会生成一个代表这个类的class对象,作为方法区这些数据的访问入口
2. 准备
为类变量分配内存并设置类变量的默认值。

如果是实例变量自然是动态运行的时候随对象创建生成在堆中赋值,但是如果是静态常量final,因为其不变性,编译器完全可以在编译阶段就直接对其初赋值。
比如 public static final int value = 33;

  1. 解析
    用直接引用替换符号引用的过程
  2. 初始化
    前面的过程都是虚拟机主导的,从这里开始执行类构造器clinit方法,可以由程序开发人员控制了。

3.类加载器

  1. 类加载器和类
    通过一个类的全限定名来获取描述此类的二进制字节流放到java虚拟机外部实现,这就是类加载器的来源。
    由同一个类加载器加载的同一份class文件在内存中的类才是相等的,换言之,加载它的类加载器和这个类本身一同确立其在JVM中的唯一性。

    相等的三种判断方式:

    1. Class对象的equlas方法、isAssignableFrom方法、isInstance方法
  2. 双亲委派模型
    顶层是 bootstrap classLoader–>Extension ClassLoader–>application classLoader
    双亲委派模型
    分别加载lib 、ext、我们的程序文件,如果我们不自定义,application是默认的应用程序类文件加载器

    对于jvm而言,世界上的classLoader只有两种,bootstrap classLoade和非bootstrap。bootstrap是jvm内部的用c/c++写的,其他的大都使用java书写的。
    application classLoader是classLoader中的getSystemClassLoader方法的返回值,一般也被成为系统类加载器。负责加载用户类路径上所指定的类库。

双亲委派模型:首先让父类加载,如果父类加载失败,继续往上请求,顶层失败,逆序反馈,最后自己加载。
我们的加载器首先让自己的父类加载,复用父类的方法,并不是通过继承来实现的,而是通过组合来实现的。

  1. 双亲模型的意义何在?
    答:无论哪一个类加载这个类,最终都会被委派到引导类加载器去完成它的加载,因此Object类在程序中的各种类加载器环境中都是一个类。这样做也保证安全性,因为如果有人想恶意置入代码,类加载器的代码就避免了这种情况的发生。

  2. 破坏双亲委派

虚拟机字节码执行引擎

这部分内容和class字节码的code属性表息息相关

栈帧结构

  1. 局部变量表在编译期间就算好最大容量,这个并不是简单的将参数,方法内变量的容量相加的,而是考虑到方法内作用域的不同,将slot复用。
  2. 方法执行的时候,参数传递:如果不是类的静态方法,即实例方法,在调用时索引号位0的slot是当前对象的引用this。方法参数然后依次排列。
  3. 操作数栈:方法开始执行的时候栈为空,执行过程中不断出栈和入栈。比如方法调用的时候就是通过操作数栈来进行参数传递。
    两个栈帧之间的数据共享
    本方法的操作数栈帧的内容可以作为被调用方法的参数(被调用方法的局部变量区和调用者的操作数栈共享数据)

  4. 动态链接
    每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。能通过该引用找到常量池地址,然后将直接引用替换符号引用。

  5. 返回地址

方法调用

基于栈的字节码解释执行引擎

书上有图:一图胜千言

其他问题

  1. 类变量:在准备阶段赋默认值,对于public static int a = 12;这种在编译期间 a = 12会被编译进 clinit初始化函数内,因此是在初始化时赋值的。
  2. 成员变量:会随对象在堆内创建的时候,赋默认值,如果我们不给值的话
  3. 方法变量和静态块内的变量都是必须在声明的时候赋值的,不然在后面的语句中如果使用,编译器编译无法通过。

参考资料

  1. http://blog.csdn.net/column/details/java-vm.html
  2. https://m.aliyun.com/yunqi/articles/2358#index_section
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值