我的JVM(五):JVM运行时数据区和JVM指令集

一、概述

    本文主要介绍java程序运行时,运行时数据区中的数据变化过程,借此来帮助我们深刻的理解jvm运行机制。

二、解析

    1. 运行时数据区

    运行时数据区Run-time data areas,如下图:

    主要包含以下六大区域:

    (1) Program Counter:程序计数器,用于存放下一条要执行的指令的位置。常用于线程切换时,记录下线程执行到了哪个位置。

    (2) JVM Stacks:java代码运行时,每一个线程对应一个栈,每一个方法对应一个Frame(栈帧)。每个栈帧包含3个组成部分:

        a. Local Variable Table:局部变量表。

        b. Operand Stack:操作数栈。对于long的处理(store and load),多数虚拟机的实现都是原子的。

        c. Dynamic Linking:动态链接。指向运行时常量池的符号链接,看这个符号链接是否已经解析,如果解析就直接取来用,没有解析就进行动态解析在使用。

        d. return address:返回值地址。如果a()方法调用b()方法,b()方法的返回值放在何处,一般是a()方法的栈帧的栈顶。

    (3) Heap:堆,所有线程共享。GC详解时分析。

    (4) Method Area:方法区,包含了运行时常量池。也是所有线程共享。在jdk1.8之前和之后是有不同的实现,如下:

        a. Perm Space (jdk<1.8):字符串常量位于PermSpace,FGC不会清理这块内容。

        b. Meta Space (jdk<=1.8):字符串常量位于堆,会触发FGC清理。

    (5) Direct Memory:jdk1.4之后增加的内容,称为直接内存。从JVM内部可以直接方法操作系统的内存。网络数据传输过来时,先存在内核空间的一块内存里,JVM需要用到时,需要将这块内容拷贝到JVM空间 ,中间有一个内存拷贝的过程。NIO可以直接访问内核空间的内存,少了内存拷贝,称为zero copy,提高效率。

    (6) Native Method Stacks:本地方法栈,jdk源码里通常有调用native修饰的本地方法,就是存在这里的。

    总结起来,多线程情况下的内存分布如下图:

    

    每个线程都有自己的Program Counter,JVM Stacks和Native Method Stacks。而Heap和Method Area以及它的两种实现都是被所有线程所共享的。

    2. 从代码到指令深度解析

    我们由两个main()方法代码进行比较:

    (1)探究 i = i++的指令,代码如下:

    输出结果如下:

    

    那么结果为何是8呢,我们根据指令来探究原理:

    (1) 通过插件查看解析二进制码

    

    (2) 进入方法区查看main()方法的指令

    

    (3) 要理解这些指令,我们需要先理解局部变量表的结构,Code目录下包含两张表,分别是行号表LineNumberTable和局部变量表LocalVariableTable,如下:

    

    (4) 局部变量表的内容是当前方法里用到的局部变量,值得注意的是局部变量表内包含传入这个方法的参数,如main()方法的参数args,如下图:

    

    此表中Name就是记录这个局部变量在常量池Constant pool中的位置,如args就在Constant pool里面的第15号,如下图:

    

    (5) i=i++指令分析

    

    我们对 i=i++ 的逐条指令进行解读:

        a. bipush 8:将byte类型的8扩展成int类型,并且将8压栈到操作数栈。

        b. istore_1:将操作数栈的8出栈,保存在局部变量表的第1号位置。到这一步就完成了int i=8这句代码。

        c. iload_1:将局部变量表位置1的值也就是i的值,也就是8又进行压栈。

        d. iinc 1 by 1:将局部变量表位置1的值进行+1,也就是执行i++。此时局部变量表中的i值是9,但是操作数栈的值还是8。

        e. istore_1:将操作数栈的8出栈,保存在局部变量表的第1号位置。相当于执行了i=i++,到这里,i的值由9被覆盖成了8。

    (6) i=++i指令分析

    源码如下:

    

    结果如下:

    

    指令如下:

        

    我们对 i=++i 的逐条指令进行解读:

        a. bipush 8:将byte类型的8扩展成int类型,并且将8压栈到操作数栈。

        b. istore_1:将操作数栈的8出栈,保存在局部变量表的第1号位置。到这一步就完成了int i=8这句代码。

        c. iinc 1 by 1:将局部变量表位置1的值进行+1,也就是执行++i。此时局部变量表中的i值是9。

        d.iload_1:将局部变量表位置1的值也就是i的值,也就是9又进行压栈。

        e. istore_1:将操作数栈的9出栈,保存在局部变量表的第1号位置。相当于执行了i=++i。

    3. 运行时数据区变化

    结合不同的代码我们来观察运行时数据区的变化:

    (1) int i = 100

    

    (2) int i= 200

    与(1)相比,这里的代码是在一个非静态方法里,那么会在常量池第0的位置内存下一个this。后面顺序是方法参数,再是方法体里的常量。

    (3) int c = a + b

    当我们调用add()方法,参数a和b分别传入3和4,过程如上图所示,常量池中依次是this(因为是非静态方法)、a、b、c。计算3+4时,会将3和4从操作数栈进行出栈再计算,并将结果7再压栈,所以计算完后,操作数栈里只有7,没有3和4。

    (4) 方法里调用别的方法

    指令解析:

        a. new #2:创建一个Hello_02对象,并将分配的内存的内存地址压栈入操作数栈。

        b. dup:将刚压栈的内存地址复制一份,用于后面构造法调用时使用。

        c. invokespecial #3:调用Hello_02的构造方法,会用掉一个操作数栈存的对象地址。

        d. astore_1:将对象赋值给常量池1号位的h。到这里就完成了Hello_02 h = new Hello_02()这句代码。

        e. aload_1:将常量池1号位的常量,也就是h压栈到操作数栈。

        f. invokevirtual #4:调用方法m1()

    由于方法调方法,会在JVM Stacks中产生很多栈帧,那么一个方法执行完后接着执行哪个方法里的哪个指令呢,这就由Return Address来记录。

    (5) 方法调用方法其他基础场景

    值得注意的是,当方法有返回值时,多了一个pop指令。这是由于m1()方法返回时,会在main()方法的栈帧的栈顶放一个100。

    (6) 递归调用

    4. 指令集分类

    指令集分两种,分别是基于寄存器的指令集和基于栈的指令集,他们的关系如下:

    (1) JVM执行指令时所采取的方式是基于栈的指令集

    (2) 基于栈的指令集主要的操作有入栈与出栈两种

    (3)  基于栈的指令集的优势在于它可以在不同平台之间进行移植,而基于寄存器的指令集是与硬件架构紧密关联的,无法做到可移植。

    (4)  基于栈的指令集的缺点在于完成相同的操作,指令数量通常要比基于寄存器的指令集数量要多;基于栈的指令集是在内存中完成操作的,而基于寄存器的指令集是直接由CPU来执行的,它是在高速缓冲区进行执行的,速度要快很多。虽然虚拟机可以采用一些优化手段,但总体来说,基于栈的指令集的执行速度要慢一些。

    5. 常用指令

    在上文中我们已经分析了很多代码的指令,现在来总结一下常用的指令:

    (1) store:出操作数栈。

    (2) load:入操作数栈。

    (3) pop:栈帧的栈顶弹出。

    (4) mul:乘法。

    (5) sub:减法。

    (6) invoke:

        a. InvokeStatic:调用静态方法。

        b. InvokeVirtual:调用虚方法

        c. InvokeInterface:调用接口方。

        d. InvokeSpecial:调用实例构造器<init>方法, 私有方法和父类方法。可以直接调用,不需要多态的方法。

        e. InvokeDynamic:lambda表达式或者反射和其他动态语音动态产生的class会用到的指令。

三、总结

    本文结合实际代码深入了解JVM常用指令以及各个指令是如何操作运行时数据区的,接下来我们就将进入GC的学习中。

    更多精彩内容,敬请扫描下方二维码,关注我的微信公众号【Java觉浅】,获取第一时间更新哦!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java觉浅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值