Android 开发简记:Java 和 Android 程序员都应该掌握的虚拟机知识
正如上图所示那样,编译的时候还是在硬盘中执行的,而运行则是在你计算机的内存中执行的,你可以理解虚拟机把这个字节码文件拿到内存中运行,而虚拟机此时会在内存中划分一块空间块,这个空间块就是拿来运行字节码文件里的代码。
而我们就是要研究这个由虚拟机划分的内存空间里的东西。
现在依然还有很多人觉得该内存空间里只有堆内存和栈内存,相信对于很多 Java 工程师来讲这两块区域应该很熟悉。实际上,严格来讲,虚拟机中的内存是划分为若干个不同的数据区域,主要 5 个:堆、方法区、虚拟机栈、本地方法栈和程序计数器。如下图所示:
而平常我们说的栈内存就是虚拟机栈,虚拟机会在虚拟机栈中会创建一个栈帧,栈帧除了用来调用方法并执行方法的,它里面还有局部变量表,操作数栈,动态连接和返回地址。
局部变量表存储每个变量值,也就是平时在方法内部定义的局部变量以及在调方法时传的参数,都是存储在局部变量表里。当虚拟机把 java 文件编译成字节码文件的时候,会对程序里的方法进行检查,然后确定每个方法需要分配的最大局部变量表的容量。
操作数栈,就是存储要进行操作的变量,是后入先出的结构栈,跟局部变量表一样也是在编译的时候就会确定好它的最大容量。当方法执行的时候,刚开始操作数栈是空的,然后随着执行的过程中会对操作数元素进行压栈和弹出。
返回地址,是确保方法在退出后返回到方法被调用的位置的地址信息。当一个方法在正常退出或异常退出完成后,虚拟机栈中的返回地址就会被拿来恢复它的上层方法执行状态。
可能说完这些概念之后还是有点抽象,以下举个例子你就明白了。这里写段代码:
以上这个方法在内存中执行的过程是这样的:
假设该方法是写在 Sum.java 文件里,虚拟机对它进行编译时会去确定好栈帧中局部变量表和操作数栈的容量,然后在创建局部变量表和操作数栈的时候根据这个容量来创建便可。
然后执行 int x = 1 的时候其实就是先将常量 1 压入操作数栈栈顶,然后再把它弹出栈并且放入到局部变量表索引为 1 的位置里,作为变量 x 的值。
接着 int y = 2 时也一样,将常量 2 压入操作数栈顶,然后再弹出来并且放入到局部变量表索引为 2 的位置里,作为 y 变量的值。
接着执行 int z = x + y 时,先将此时局部变量表里的值 1 和值 2 压入到操作数栈中,此时栈顶是 2,底下是 1,然后进行加法操作得到值 3,然后此时栈顶就是该结果值 3,将该值 3 出栈,存入到局部变量表索引为 3 的位置。
最后执行 return z 的时候,将局部变量表中的 3 压入回操作数栈栈顶,然后将操作数栈栈中的 3 返回给上层方法。到这里整个 sum 方法执行完毕,而布局变量表和操作数栈也会跟着销毁。
以上过程如果要画成图可以这样表示:
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2
ltYWdlcy8xMDk1OTAwLWFmNmRmYTE3ZWI5M2ZhN2Q?x-oss-process=image/format,png)
整个流程图虽然很长,但结构非常容易理解,要操作的元素都出入操作数栈,而变量值则存按索引位置存到局部变量表里,请结合上文五点步骤描述来理解此图。
最后也可以使用 javap 命令来查看该类的字节码指令,验证是否像上图描述的流程一样执行该方法。这点读者可自行去确认,这里就不作讲解了。
堆内存这块区域则是存放对象实例的,大家应该不陌生了,当堆内存中的对象没有被引用指向时,就变成了可回收对象,被 GC 进行垃圾回收。
评论