关于JVM系列的文章,都是在读了《深入理解java虚拟机》一书之后的读书笔记总结。
首先,看图说话(下图是java虚拟机运行时数据区图):
根据上图,我们可以发现java虚拟机运行时内存共划分成了5个区,分别是:方法区(method area)、堆(heap)、虚拟机栈(vm stack)、本地方法栈(native method stack)和程序计数器(program counter register).其中,方法区和堆属于所有线程共享的内存区域。
程序计数器
从名字我们大概可以看到,这个其实是用来指示当前虚拟机所执行的字节码所在的指令地址(对于native方法而言,这个内存的值是空)。
为了在线程切换的时候,能够正确加载线程的执行位置,就需要根据这个计数器来指示,因此这个内存区是线程私有的,每个线程都会具有自己的程序计数器,互不影响。
java虚拟机栈
虚拟机栈(也就是我们通常论述的java堆栈中的“栈”),它描述的是java方法的执行的内存模型,它是一个栈,栈里面的存放的元素我们称之为栈帧。因此每个方法的生命周期其实就是栈帧的出栈和入栈道过程。栈帧的具体内容如下图所示:
由上图我们可以发现栈帧里面包含了局部变量表、操作数栈、动态连接、返回地址以及其他的一些额外信息等。其中,在程序编译的时候,就已经确定了局部变量表、和操作数栈的大小(栈帧占用内存的大小在编译的时候就已经确定)
局部变量表
局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。包括编译期间可知的各种基本数据类型(boolean、byte、short、int、float、char、long、double,其中long和double使用了两个局部变量空间来表示)、对象引用类型(对象实例的引用(类似于指针),通过它可以能直接或者间接的查找到对象在堆中数据存放的起始地址索引,并且能查找到对象所属类型在方法区中的存储的类型信息)和returnAddress类型。
虚拟机通过索引定位的方式使用局部变量表,索引值的范围是0-局部变量表最大的slot(局部变量表的最小容量单位,一个slot可以存储除long和double以外的任意一个基本数据类型)数量。在执行实例方法(即非static限定的方法)的时候,局部变量表的第0个slot默认是用于传递方法所属对象的实例的引用(也就是我们使用的this对象),其余参数则按照参数表顺序排列。
操作数栈
一个方法刚开始执行的时候,操作数栈为空,在执行过程中,各种字节码指令会进行入栈和出栈的操作(比如算术运算的时候和方法调用的时候)。值得注意的是,操作数栈中的元素类型必须于字节码指令序列严格匹配。
本地方法栈
和java虚拟机栈相类似,只是虚拟机栈是为java虚拟机执行java方法服务的,而本地方法栈是为虚拟机使用native方法服务的。
堆
堆是所有线程共享的一块内存区域,在虚拟机启动的时候就被创建出来。这片内存区用于存放对象实例,几乎所有的对象实例都在这里分配内存。堆区是java虚拟机垃圾收集器管理的主要区域。
方法区
方法区也是属于线程共享的一块内存区域,它用于存储已被java虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
运行时常量池
方法区的一部分,用于存放编译期生成的各种字面量和符号引用。