根据JVM内存规范,JVM大致可以分为下图中几个部分:
(图片来源网络)
上图中灰色的部分就是JVM的运行时数据区了,这个区域可以分为:PC寄存器(程序计数器),java虚拟机栈,java堆和方法区。绿色部分的java堆和方法区为下面分点介绍:
1. PC寄存器(程序计数器)
因为JVM可以支持多线程并行,所以每一条线程都会有一个自己的PC寄存器。在任意时刻,一条java线程只会执行一个方法的代码,如果这个方法不是native方法,那么PC寄存器就会记录这根线程正在执行的java字节码指令的地址,如果这个方法是native的,则此时PC寄存器的值为undefined。
2. java虚拟机栈
每一根线程都有就自己的java虚拟机栈(java Virtual Machine stack) ,这个栈与线程同时被创建,用于存储栈帧(方法被调用时为方法创建),他是java中方法执行的地方。java虚拟机栈与其他语言中的栈非常类似,可以存储局部变量和一些尚未算好的中间值。这里需要区分“java栈”和“栈”的概念,因为JVM实质就是其他语言(Hotspot 就是由c++实现的)实现的一个应用程序,所以java虚拟机栈在它的实现角度下看来可能分配在堆中。
3. java堆
java堆是JVM运行时数据区中一个线程共享的内存区域,也是各种对象被创建的地方。java堆在虚拟机启动时就被创建了,它负责存储被GC(garbage collector)所管理的各种对象,这些对象无需也无法显示的销毁。
4. 方法区
在JVM中,方法区是一个可供各个线程共享的运行时内存空间。这一点与java堆比较相似,不同的是方法区存储的是每一个类的结构信息,如:运行时常量池,静态字段,构造方法和普通方法的字节码内容等。
5. 本地方法栈
因为JVM是由其他语言编写的,所以在JVM运行时可能会调用一些native方法,这些方法是由其他语言来实现的,当这些方法被调用时会被加载到本地方法栈中运行,本地方法栈其实就是传统意义上的栈(通常称为C stack),本地方法栈也是线程私有的,会在线程被创建的时候按线程分配。
6. 栈帧
这里栈帧其实是属于java栈里的内容,它是方法执行的载体,因为比较重要所以单独拿出来说说:
栈帧的结构可以分为1.局部变量标 2.操作数栈 3.动态链接 4.返回地址
1 . 局部变量表:
每个栈帧都包含一组称为局部变量表的变量列表,用来保存方法运行期间所产生的各种局部变量,局部变量表的长度在编译期就已经确定了。局部变量表中可以存储八大数据类型以及reference(引用)和returnAddress(方法的返回地址),但是long和double类型的变量会占用两个连续的局部变量的位置。局部变量表使用索引来定位存储的位置,索引值从0开始,并且只能为整数
2 . 操作数栈
操作数栈是栈帧中用来对数据进行操作的地方,其大小也是编译期决定的。java虚拟机提供一些字节码指令用来从局部变量表,对象实例或是方法区中复制常量或变量到操作数栈中进行运算。如:iload(将局部变量表中指定数据推送至操作数栈顶),iaload(将int类型指定索引的值推送至操作数栈顶),ldc(从运行时常量池中取得指定数据推送至操作数栈顶)。也提供了对数据进行操作的指令,如:iadd(加),is(减),imul(乘),idiv(除)等等。。。具体可以参考jvm指令集手册。
3 .动态链接
每个栈帧内部都包含了一个指向当前方法所在类型的运行时常量池的引用,以便对当前方法实现动态链接,由于我们在程序中定义的引用其实是一种符号引用,如String str = “abc”;这里的a在栈中只是一个代表符号而已,要想真正获得对常量池中“abc”的直接引用就必须依靠动态链接来完成从符号引用到直接引用的转换,从符号引用到直接引用的解析早在类加载阶段就完成了。
4 . 返回地址
返回地址中存储了当前方法返回值要到达的位置,值得提的是在当前方法执行完成返回值时,当前方法还负责恢复调用者的状态。包括恢复调用者的局部变量表和操作数栈,以及正确的递增程序计数器,调用者的代码在当前方法的返回值压入调用者栈帧的操作数栈后才会继续执行。
到这里关于JVM的结构分析就结束了。欢迎大家和我交流,一起成长
参考文献:《java虚拟机规范(javaSE 8版)》