四、运行时数据区
JVM内存结构
运行时数据区
》堆
对于大多数应用来说,堆(Heap)是JVM虚拟机所管理的内存中最大的一块。堆主要用来存放对象实例。
堆是被所有线程共享的一块内存区域,在虚拟机启动时创建此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时会出现OutOfMemoryError异常。
》方法区(元空间1.8)
方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载
类信息
常量
静态变量
即时编译器编译后的代码等数据。
相对而言,垃圾收集行为在这个区域比较少出现,但并非数据进了方法区就永久的存在了,这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,当方法区无法满足内存分配需要时,将抛出OutOfMemoryError异常。
》栈
Java栈是Java方法执行的内存模型每个方法在执行的同时都会创建一个栈帧的用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程就对应着一个栈帧在虚拟机中入栈和出栈的过程。
Java 6中,32位Sparc系统默认的栈内存是512k,64位系统是1024k; 32位的 X86 Solaris/linux系统默认栈内存是320k,64位 X86 Solaris/linux系统默认栈内存是1024k;Windows中默认的栈内存是从java.exe中读出来的。对Java 6来说,32位系统默认大小为320k,64位系统为1024k。
&栈帧(Stack Frame)
栈帧由三部分组成:局部变量表、操作数栈以及帧数据。
栈帧的大小因局部变量表和操作数栈而异。当JVM执行一个方法时,它会检查class中的数据,以便确定一个方法执行时在局部变量表和操作数栈中所需存储的word size。然后,JVM会为当前方法创建一个size相对应的栈帧,然后把它push到栈顶。
一个栈帧需要分配多少内存,在编译的时候已经确定,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
&局部变量表
LocalVariable Table。用于保存函数的参数以及局部变量用的,局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁。局部变量表在编译期确定大小。
》在编译程序代码的时候就可以确定栈帧中需要多大的局部变量表,具体大小可在编译后的 Class 文件中看到。
》局部变量表的容量以 Variable Slot(变量槽)为最小单位,每个变量槽都可以存储 32 位长度的内存空间。
》在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法,那局部变量表中第 0 位索引的 Slot 默认是用于传递方法所属对象实例的引用(在方法中可以通过关键字 this 来访问到这个隐含的参数)。
》其余参数则按照参数表顺序排列,占用从 1 开始的局部变量 Slot。
》基本类型数据以及引用和 returnAddress(返回地址)占用一个变量槽,long 和 double 需要两个。
&操作数栈
OperandStack。主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。只支持出栈入栈操作。同样也可以在编译期确定大小。
》栈帧被创建时,操作栈是空的。操作栈的每个项可以存放 JVM 的各种类型数据,其中 long 和 double 类型(64位数据)占用两个栈深。
》方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作(与 Java 栈中栈帧操作类似)。
》操作栈调用其它有返回结果的方法时,会把结果 push 到栈上(通过操作数栈来进行参数传递)。
&动态连接
》每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
》在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。另外的一部分将在运行时转化为直接引用,这部分称为动态链接。
&返回地址
方法开始执行后,只有 2 种方式可以退出 :方法返回指令,异常退出。
&帧数据区
栈帧需要一些数据来支持常量池解析、正常方法返回和异常处理等。在帧数据区中保存着访问常量池的指针,
方便程序访问常量池。帧数据区的大小依赖于 JVM 的具体实现。