JVM学习笔记(五、运行时数据区)
目录:
- 简介
- 堆
- 方法区(元空间)
- 栈
简介
运行时数据区分为两类,一类是线程间共享的方法区和堆,另一类是线程私有的虚拟机栈、本地方法栈以及程序计数器。
堆
对于大多数应用来说,Java堆(Heap)是Java虚拟机所管理的内存中最大的一块,它是用来存放对象实例。
- 堆是被所有线程共享的内存区域,虚拟机启动时创建。
- 堆的目的是存放对象实例,所有实例都在此分配内存。
- 如果在堆中没有内存完成实例的分配,并且堆也无法扩展时,抛出OutOfMemoryError异常。
方法区(元空间)
方法区(元空间):它也是被各线程共享的区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
相对而言,GC在这个区域不长出现,但并不是不会出现。此区域的GC的主要目标是常量池的回收、类型的卸载。
当方法区无法满足内存分配的需要时,抛出OutOfMemoryError异常。
上面有提到即时编译器编译后的代码,是什么意思呢?
首先我们要知道Java是解释型语言(当然这是Java1.0前的版本,之后的版本已经优化,是解释+编译),其速度肯定是不如C这种编译型的语言快。
那怎么办呢,SUN的做法就是,既然一行行的解释运行慢,那我就把热点字节码抽出来先编译成可执行的机器码,这样就不会解释重复的代码,那效率肯是会大幅提升的。
而即时编译器编译后的代码正式上面说道的热点代码编译,也就是JVM的优化JIT。
栈
Java栈是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每个方法从调用直至执行完成的一系列过程都对应着一个栈帧在虚拟机中入栈和出栈的过程。
栈的内存大小:
- 对Java 6来说,2位Sparc系统默认的栈内存是512k,64位系统是1024k。
- 对Java 6来说,32位的X86 Solaris/linux系统默认栈内存是320k,64位X86 Solaris/linux系统默认栈内存是1024k。
- 对Java 6来说,Windows中默认的栈内存是从java.exe中读出来的。
- 对Java 6来说,32位系统默认大小为320k,64位系统为1024k。
1、栈帧(Stack Frame):
栈帧由三部分组成:局部变量表、操作数栈以及帧数据。
栈帧的大小因局部变量表和操作数栈而异。当JVM执行一个方法时,它会检查class中的数据,以便确定一个方法执行时在局部变量表和操作数栈中所需存储的word size。
然后,JVM会为当前方法创建一个size相对应的栈帧,然后把它push到栈顶。
注意:一个栈帧需要分配多少内存,在编译的时候已经确定,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
2、局部变量表(Local Variable Table):
用于保存函数的参数以及局部变量用的,局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁。局部变量表在编译期确定大小。
- 在编译时就可以确定栈帧中需要多大的局部变量表,具体大小可在编译后的Class文件中看到。
- 局部变量表的容量以Variable Slot(变量槽)为最小单位,每个变量槽都可以存储32位长度的内存空间。
- 在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程。
- 如果执行的是实例方法,那局部变量表中第0位索引的Slot默认是用于传递方法所属对象实例的引用,也就是this关键字。
- 其余参数则按照参数表顺序排列,占用从1开始的局部变量Slot。
- 基本类型数据以及引用和returnAddress(返回地址)占用一个变量槽,long和double需要两个。
总结:
- 栈帧需要的局部大小是编译时确定,可在字节码文件查看。
- 局部变量表最小容量单位是Variable Solt,每个槽可存储32位长度的内存空间。
- 方法执行传参时,实例方法第0个槽是的占用为this,其它参数依次往后。
- 基本类型、应用类型、返回地址仅栈一个槽,long、double栈两个。
3、操作数栈(Operand Stack):
主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。只支持出栈入栈操作,同样也可以在编译期确定大小。
- 栈帧被创建时,操作栈是空的。操作栈的每个项可以存放JVM的各种类型数据,其中long和double类型(64位数据)占用两个栈深。
- 方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作(与 Java 栈中栈帧操作类似)。
- 操作栈调用其它有返回结果的方法时,会把结果push到栈上(通过操作数栈来进行参数传递)。
4:、动态连接:
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。另外的一部分将在运行时转化为直接引用,这部分称为动态链接。
举个例子:
1 public class Person { 2 3 public static void main(String[] args) { 4 Person person = new Male(); 5 } 6 7 } 8 9 class Male extends Person { 10 }
person引用在编译时无法确认其类型,只有在运行期才能动态的获取类型,这就是动态的链接引用如实例关系。
当然动态链接不仅仅只是多态,像反射等也是。
5、返回地址:
方法开始执行后,只有2种方式可以退出 :方法返回指令,异常退出。
6、帧数据区:
栈帧需要一些数据来支持常量池解析、正常方法返回和异常处理等。在帧数据区中保存着访问常量池的指针,方便程序访问常量池。帧数据区的大小依赖于JVM的具体实现。
7、程序计数器(Program Counter Register):
一块较小的内存空间,作用是存放下一条指令所在单元的地址的地方。