JVM_05 运行时数据区与指令集

JVM Runtime data area and JVM instructions

<previous next>

一、Java 内存布局

在这里插入图片描述

图 1 java 内存布局
PC

线程私有

program counter , 保存下一条指令的位置。 PC 是唯一不会发生 OOM 的内存区域

虚拟机执行的过程类似于以下的伪代码:

while (not end) {
  get from pc;
  get 对应位置指令;
  执行该指令;
  pc ++;
}
JVM Stack

线程私有

​ 里面装的是栈桢,每执行一次方法调用就意味着一次栈桢的压栈。每次方法返回都意味着一次栈桢的出栈

在这里插入图片描述

图 2 虚拟机栈结构
  • local veriables 局部变量表

    保存方法的局部变量

  • operand stacks 操作数栈

    JVM 使用一种基于栈的指令集,在执行运算时,是在操作数栈上执行的

  • dynamic linking

    指向 constant pool 的一条符号链接,其类型为 CONSTANT_Dynamic

    public static class CONSTANT_Dynamic_info extends CpInfo {
        /*
                CONSTANT_Dynamic_info {
                 u1 tag;
                 u2 bootstrap_method_attr_index;
                 u2 name_and_type_index;
                }
         */
        // valid index into the bootstrap_methods
        private Integer bootstrap_method_attr_index;
        private CONSTANT_NameAndType_info name_and_type_index;
    }
  • return address 返回地址

    存储方法调用完成后要返回的地址

Heap

线程共享

堆区存放实例对象,是 GC 的重点工作区

Method Area

线程共享

装 T.class 文件等

  • 1.8 之前 -> Perm Space
  • 1.8 之后 -> Meta Space
Runtime constant pool

class 文件中的常量表,在运行时保存在这里

Native Method Stack

线程私有

本地方法栈为虚拟机使用到的Native方法服务

在这里插入图片描述

图 3 JVM 工作模型

二、Java代码执行过程

先来看一小段程序:

public class Test {
    public static void main(String[] args) {
        int i = 8;
        i = i++;
        System.out.println(i);

        int j = 8;
        j = ++j;
        System.out.println(j);
    }
}

以上代码的运行结果是什么呢?

> Task :Test.main()
8
9

​ 我们都知道 i++ 是先用后加,++i 是先加后用。那么在虚拟机层面这两个操作是如何实现的呢?

代码1:
public class Test {
    public static void main(String[] args) {
        int i = 8;
        i = i++;
        System.out.println(i);
    }
}

虚拟机指令:

 0 bipush 8
 2 istore_1
 3 iload_1
 4 iinc 1 by 1
 7 istore_1
 8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return

方法在执行的时候,使用 局部变量表和操作数栈相互配合以完成指令。

指令地址指令解释
0bipush 8将一个 byte push 到操作数栈, 并扩展成为 int 值
2istore_1出栈一个 int 型变量 store 到局部变量表(下标是 1)
3iload_1从局部变量表 load int 值入操作数栈(只有到操作数栈里才可以操作)
4iinc 1 by 1将局部变量表中元素出栈自增1 (注意此命令不改变操作数栈)
7istore_1出栈一个 int 型变量 store 到局部变量表(下标是 1)(这一步把之前加过1的值给覆盖掉了)
8getstatic #2 <java/lang/System.out>加载一个 static 的 PrintStream System.out
11iload_1从局部变量表 load int 值入操作数栈
12invokevirtual #3 <java/io/PrintStream.println>调用 println 方法,出栈一个元素做为其参数
15return返回
代码2:
public class Test {
    public static void main(String[] args) {
        int i = 8;
        i = i++;
        System.out.println(i);
    }
}

虚拟机指令:

 0 bipush 8
 2 istore_1
 3 iinc 1 by 1
 6 iload_1
 7 istore_1
 8 getstatic #2 <java/lang/System.out>
11 iload_1
12 invokevirtual #3 <java/io/PrintStream.println>
15 return

方法在执行的时候,使用 局部变量表和操作数栈相互配合以完成指令。

指令地址指令解释
0bipush 8将一个 byte push 到操作数栈, 并扩展成为 int 值
2istore_1出栈一个 int 型变量 store 到局部变量表(下标是 1)
3iinc 1 by 1将局部变量表中元素出栈自增1 (注意此命令不改变操作数栈)
6iload_1从局部变量表 load int 值入操作数栈(只有到操作数栈里才可以操作)
7istore_1出栈一个 int 型变量 store 到局部变量表(下标是 1)(这一步把之前加过1的值给覆盖掉了)
8getstatic #2 <java/lang/System.out>加载一个 static 的 PrintStream System.out
11iload_1从局部变量表 load int 值入操作数栈
12invokevirtual #3 <java/io/PrintStream.println>调用 println 方法,出栈一个元素做为其参数
15return返回
代码总结

上述代码只有两行代码的顺序不同:

i++ 是先将局部变量表的目标值入操作数栈,然后在对局部变量表上的元素进行了自增

++i 是先将局部变量表上的元素进行了自增,然后将自增后的值入操作数栈

上述代码2稍作改动后:

public class Test {
    public static void main(String[] args) {
        int i = 8;
        int j = i++;
        System.out.println(j);
    }
}
 0 bipush 8
 2 istore_1
 3 iinc 1 by 1
 6 iload_1
 7 istore_2
 8 getstatic #2 <java/lang/System.out>
11 iload_2
12 invokevirtual #3 <java/io/PrintStream.println>
15 return

​ 可以看到,相对应改变的点只有 原来的 istore_1 变为了 istore_2, iload_1 变为了 iload_2。这是因为赋值给了变量 j 导致的。

​ 所以 i = i++ 等同于 i = i 。进行了一次无用的赋值,并且 i 自增的这个操作实际是做了的,不过把自增的结果直接丢弃了;

​ 而 i = ++i 等同于 i = i + 1

如果没有赋值操作:
public class Test {
    public static void main(String[] args) {
        int i = 8;
        i++;
        System.out.println(i);
    }
}
 0 bipush 8
 2 istore_1
 3 iinc 1 by 1

 6 getstatic #2 <java/lang/System.out>
 9 iload_1
10 invokevirtual #3 <java/io/PrintStream.println>
13 return
public class Test {
    public static void main(String[] args) {
        int i = 8;
        ++i;
        System.out.println(i);
    }
}
 0 bipush 8
 2 istore_1
 3 iinc 1 by 1
 
 6 getstatic #2 <java/lang/System.out>
 9 iload_1
10 invokevirtual #3 <java/io/PrintStream.println>
13 return

​ 在没有赋值操作的情况下,i++++i 生成的虚拟机指令是相同的

方法调用总结

​ 方法调用,就是对应一次虚拟机栈的压栈和出栈过程。方法调用时入栈,并将所需要的变量放在栈桢的局部变量表中。其中非静态方法的局部变量表中,下标是0的变量为 this 指针。然后按照方法参数,方法内局部变量的顺序(也就是变量在方法中出现的先后顺序),依次排列

​ 方法返回时,如果有返回值,则操作数栈的栈顶元素作为返回值返回

new 一个对象的过程
0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init>>
7 astore_1
8 return
指令地址指令解释
0new #2 <java/lang/Object>创建一个对象,并压栈
3dup将栈顶地址复制,再压栈
4invokespecial #1 <java/lang/Object.>出栈执行构造方法
7astore_1出栈一个赋值给 o
8return

三、指令集与指令

指令集分类
  1. 基于寄存器的指令集
  2. 基于栈的指令集
    Hotspot中的 Local Variable Table = JVM中的寄存器
关键指令
store

出栈一个值 (这个值必须是引用类型) 赋值到局部变量表指定下标

load

从局部变量表指定下标处的引用入栈

bipush

把一个 byte 压栈,后面参数是具体数值

pop

出栈栈顶的元素(必须是一个字节的,因此不能是 Long 或者 Double)

pop2

出栈两字节元素(必须是 Long 或者 Double 或者两个其他一字节元素)

imul

栈顶两 int 相乘,结果入栈。lmul, fmul, dmul 分别处理 Long, Float, Double 类型

isub

栈顶两 int 相减,结果入栈。

invoke
  1. InvokeStatic - 调用静态方法

  2. InvokeVirtual - 调用可多态的非静态方法

  3. InvokeInterface - 调用接口回调对象的方法

  4. InovkeSpecial - 调用可以直接定位,不需要多态的方法
    private 方法 , 构造方法

  5. InvokeDynamic - 动态产生的class,会用到的指令

    JVM最难的指令

    1. lambda 表达式
    2. 反射
    3. 其他动态语言scala kotlin
    4. 或者CGLib ASM

<clinit> Class 的init -> 静态语句块

<init> 构造方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值