3: istore_2 将int类型值存入局部变量2 -->意思是将int b=2;中的变量b存入局部变量表中第三个位置, 然后让操作数栈中的数字2出栈, 给局部变量表中的b赋值为2
4: iload_1 从局部变量1中装载int类型值--->这句话的意思是, 将操作数1从操作数栈取出, 转入局部变量表中的a, 现在局部变量表中a=1
要想更好的理解iload_1,我们要先来研究程序计数器。
程序计数器
在JVM虚拟机中,程序计数器是其中的一个组成部分。
程序计数器是每一个线程独有的, 他用来存放马上要执行的那行代码的内存位置, 也可以叫行号. 我们看到jvm反编译代码里,都会有0 1 2 3这样的位置(如下图), 我们可以将其认为是一个标识.而程序计数器可以简单理解为是记录这些数字的. 而实际上这些数字对应的是内存里的地址
当字节码执行引擎执行到第4行的时候,将执行到4: iload_1, 我们可以简单理解为程序计数器记录的代码位置是4. 我们的方法Math.class是放在方法区的, 由字节码执行引擎执行, 每次执行完一行代码, 字节码执行引擎都会修改程序计数器的位置, 让其向下移动一位
java虚拟机为什么要设计程序计数器呢?
因为多线程。当一个线程正在执行, 被另一个线程抢占了cpu, 这时之前的线程就要挂起, 当线程2执行完以后, 再执行线程1. 那么线程1之前执行到哪里了呢? 程序计数器帮我们记录了.
下面执行这句话
4: iload_1 从局部变量1中装载int类型值--> 意思是从局部变量表的第二个位置取出int类型的变量值, 将其放入到操作数栈中.此时程序计数器指向的是4
5: iload_2 从局部变量2中装载int类型值-->意思是将局部变量中的第三个int类型的元素b的值取出来, 放到操作数栈, 此时程序计数器指向的是5
6: iadd 执行int类型的加法 ---> 将两个局部变量表中的数取出, 进行加法操作, 此操作是在cpu中完成的, 将执行后的结果3在放入到操作数栈 ,此时程序计数器指向的是6
7: bipush 10 :将一个8位带符号整数压入栈 --> 这句话的意思是将10压入操作数栈
我们发现这里的位置是7, 但是下一个就变成了9, 那8哪里去了呢? 其实这里的0 1 2 3 ...都是对应的内存地址, 我们的乘数10也会占用内存空间, 所以, 8的位置存的是乘数10
9: imul 执行int类型的乘法 --> 这个和iadd加法一样, 首先将操作数栈中的3和10取出来, 在cpu里面进行计算, 将计算的结果30在放回操作数栈
乘法操作是在cpu的寄存器中进行计算的. 我们这里说的都是保存在内存中.
10: istore_3 将int类型值存入局部变量表中 ---> 意思是是将c这个变量放入局部变量表, 然后让操作数栈中的30出栈, 赋值给变量c
11: iload_3 从局部变量3中装载int类型值 --> 将局部变量表中取出第4个位置的值30, 装进局部变量表
12: ireturn 从方法中返回int类型的数据 --> 最后将得到的结果c返回.
这个方法中的变量是如何在操作数栈和局部变量表中转换的, 我们就知道了. 现在应该可以理解操作数栈和局部变量表了吧~~~
总结:什么是操作数栈?**
在运算的过程中, 常数1, 2, 10, 也需要有内存空间存放, 那么它存在哪里呢? 就保存在操作数栈里面
操作数栈就是在运行的过程中, 一块临时的内存中转空间
4.3.3 动态链接
在之前说过什么是动态链接: 参考文章:
https://www.cnblogs.com/ITPower/p/13197220.html 搜索:动态链接
静态链接是在程序加载的时候一同被加载进来的. 通常用静态常量, 静态方法等, 因为他们在内存地址中只有一份, 所以, 为了性能, 就直接被加载进来了
而动态链接, 是使用的时候才会被加载进来的链接, 比如compute方法. 只要在执行到math.compute()方法的时候才会真的进行加载.
4.3.4 方法出口
当我们运行完compute()方法以后, 还要返回到main方法的math.comput()方法的位置, 那么他怎么返回回来呢?返回回来以后该执行哪一句代码了呢?在进入compute()方法之前,就在方法出口里记录好了, 我应该如何返回,返回到哪里. 方法出口就是记录一些方法的信息的.
五. 堆和栈的关系
上面研究了compute()方法的栈帧空间,再来看一下main方法的栈帧空间。整体来说,都是一样的,但有一块需要说明一下,那就是局部变量表。来看看下面的代码
public static void main(String[] args) { Math math = new Math(); math.compute(); }
main方法的局部变量和compute()有什么区别呢? main方法中的math是一个对象. 我们知道通常对象是被创建在堆里面的. 而math是在局部变量表中, 记录的是堆中new Math对象的地址。
说的明白一些,math里存放的不是具体的内容,而是实例对象的地址。
那么栈和堆的关系就出来了, 如果栈中有很多new对象, 这些对象是创建在堆里面的. 栈里面存的是这些堆中创建的对象的内存地址。
六. 方法区
我们可以通过javap -v Math.class > Math.txt命令, 打印更详细的jvm反编译后的代码
这次生成的代码,和使用javap -c生成的代码的区别是多了Constant pool常量池。这些常量池是放在哪里的呢?放在方法区。这里看到的常量池叫做运行时常量池。还有很多其他的常量池,比如:八大数据类型的对象常量池,字符串常量池等。
这里主要理解运行时常量池。运行时常量池放在方法区里。
方法区主要有哪些元素呢?
常量 + 静态变量 + 类元信息(就是类的代码信息)
在Math.class类中, 就有常量和静态常量
public static int initData = 666; public static User user = new User();
他们就放在方法区里面. 这里面 new User()是放在堆里面的, 在堆中分配了一个内存地址,而user对象是放在方法区里面的. 方法区中user对象指向了在堆中分配的内存空间。
堆和方法区的关系是: 方法区中对象引用的是堆中new出来的对象的地址
类元信息: Math.class整个类中定义的内容就是类元信息, 也放在方法区。
七. 本地方法栈
本地方法栈是有c++代码实现的方法. 方法名带有native的代码.
比如:
new Thread().start();
这里的start()调用的就是本地方法
这就是本地方法
本地方法栈: 运行的时候也需要有内存空间去储存, 这些内存空间就是本地方法栈提供的
每一个线程都会分配一个栈空间,本地方法栈和程序计数器。如上图main线程:包含线程栈,本地方法栈,程序计数器。