内存是非常重要的系统资源,是硬盘和 CPU 的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 在运行过程中内存申请、分配、管理的策略,保证了 JVM 的高效稳定运行。不同的 JVM 对于内存的划分方式和管理机制存在着部分差异。Java 虚拟机规范定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。另外一些则是与线程一一对应的,这些与线程一一对应的数据区域会随着线程开始和结束而创建和销毁。
本文基于作者2014年发布文章<JVM规范定义运行时数据区详解-Java快速进阶教程>创作。相对旧文而言,本文加强实例池、句柄池、实例指针、类型指针、对象引用 、对象类型数据等概念的解释。
下图是 Java 虚拟机规范中定义的各种运行时数据区域。

Java 虚拟机可以支持多个线程同时执行,每个 Java 虚拟机线程都有自己的(program counter)寄存器。在任何时候,每个Java 虚拟机线程都只能执行单个方法的代码。这个正被执行的方法称为该线程当前方法(current method)。如果该方法不是native调用,则寄存器里存放的内容就是Java 虚拟机当前正在执行的指令的地址 。如果这个方法是native调用,那么Java 虚拟机的寄存器存放的内容就是undefined。同时Java 虚拟机寄存器容量至少能够存放得下与平台相关指针或者规范中定义的returnAddress类型的尺寸。
3.1 栈定义
Java 虚拟机栈(Java Virtual Machine Stacks),早期也叫 Java 栈。每个线程在创建的时候都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次 Java 方法调用,是线程私有的,生命周期和线程一致。
与传统语言(例如C语言) 中的栈非常类似, 用于存储局部变量与一些尚未算好的结果。另外, 它还可以出现在方法调用和返回。
栈因为除了栈帧的出栈和入栈之外,Java虚拟机栈不会再受其他因素的影响, 所以栈帧可以在堆中分配Java虚拟机栈所使用的内存不需要保证是连续的。另外规范既允许Java虚拟机栈被实现成固定大小,也允许根据计算动态来扩展和收缩。 如果采用固定大小的Java虚拟机栈, 那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。
栈中可能出现的异常:
Java 虚拟机规范允许 Java虚拟机栈的大小是动态的或者是固定不变的
栈中存储什么?
栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
每个栈帧(Stack Frame)中存储什么?


maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的max_stack 数据项中栈中的元素可以是任意的 Java 数据类型
方法调用不同于方法执行,方法调用阶段的唯一任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。Class 文件的编译过程中不包括传统编译器中的连接步骤,一切方法调用在 Class文件里面存储的都是符号引用,而不是方法在实际运行时内存布局中的入口地址(直接引用)。也就是需要在类加载阶段,甚至到运行期才能确定目标方法的直接引用。在 JVM 中,将符号引用转换为调用方法的直接引用与方法的绑定机制有关,常用方式有2种:
对应的方法的绑定机制为:早期绑定(Early Binding)和晚期绑定(Late Binding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。
在面向对象编程中,会频繁的使用到动态分派,如果每次动态分派都要重新在类的方法元数据中搜索合适的目标有可能会影响到执行效率。为了提高性能,JVM 采用在类的方法区建立一个虚方法表(virtual method table),使用索引表来代替查找。非虚方法不会出现在表中。
每个类中都有一个虚方法表,表中存放着各个方法的实际入口。
虚方法表会在类加载的连接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM 会把该类的方法表也初始化完毕。
用来存放调用该方法的 PC 寄存器的值。
一个方法的结束,有两种方式
无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的 PC 计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定的,栈帧中一般不会保存这部分信息。
当一个方法开始执行后,只有两种方式可以退出这个方法:
本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去。
正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值
3.2.5 附加信息
栈帧中还允许携带与 Java 虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息,但这些信息取决于具体的虚拟机实现。
本文内容来自于Java® 1.7 语言规范 的理解.
句柄池是一种管理对象句柄的机制,它可以缓存对象的句柄并在需要时重复使用它们,从而避免频繁创建和销毁句柄的开销,提高系统的性能和效率。在java中句柄池也是堆中的一部分,它存储着对象句柄的引用。Java虚拟机通过句柄池来管理对象句柄的创建和销毁,以提高系统的性能和效率。当程序需要引用一个对象时,虚拟机会先从句柄池中获取该对象的句柄,然后再通过句柄来访问对象实例。
减少对象句柄的创建和销毁次数,提高系统的性能和效率。
创建和销毁对象句柄需要消耗大量的系统资源,尤其是在多线程和高并发的环境下,频繁创建和销毁对象句柄会导致系统性能下降
句柄池用于存储Java对象的引用,它是实例池的一种优化方式。在句柄池中,每个Java对象都有一个句柄,句柄包含了对象的实际地址和类型信息。引用变量实际上是指向句柄而不是对象本身。句柄池的构成包括以下几个方面:
对象引用地址 对象引用地址是句柄中最重要的部分,它指向了Java对象的实际地址。在Java虚拟机中,对象引用地址是一个指针,指向Java对象在堆内存中的实际地址。
类型信息指针 类型信息指针是句柄中保存的Java对象的类型信息。在Java虚拟机中,每个Java对象都有一个类型信息指针,用于存储对象的类型信息。类型信息指针指向的是一个类型信息对象,包含了Java对象的类型信息,例如类的名称、父类的类型信息、实现的接口信息等。
其他信息 句柄中还可能包含一些其他的信息,例如锁信息、GC状态等。锁信息用于存储Java对象的锁状态,GC状态用于标记Java对象是否需要进行垃圾回收等。
总的来说,Java句柄是一种优化方式,可以减少Java虚拟机的内存使用,提高程序的性能和效率。句柄本身由对象引用地址、类型信息指针和其他信息等组成,可以有效地管理Java对象的内存空间。
实例池是一种管理实例对象的机制,它可以缓存已经创建的对象并在需要时重复使用它们,从而避免频繁创建和销毁对象的开销,提高系统的性能和效率。在Java中实例池是堆中的一部分,它存储着已经创建的对象实例。Java虚拟机通过实例池来管理对象实例的创建和销毁,以提高系统的性能和效率。当程序需要创建一个新的对象时,虚拟机会先查找实例池中是否存在相同类型的对象实例,如果存在,则直接返回该对象实例的引用,否则就创建一个新的对象实例,并将其加入到实例池中。
减少对象的创建和销毁次数,提高系统的性能和效率。
创建和销毁对象需要消耗大量的系统资源,尤其是在多线程和高并发的环境下,频繁创建和销毁对象会导致系统性能下降。
实例池用于存储Java对象的实例,包括数组和类实例。实例池中的对象可以通过引用变量来访问和操作。实例池的构成包括以下几个方面:
实例指针是一种指向对象实例的指针,它可以在程序中引用和操作对象实例。
指向对象实例,方便程序引用和操作对象。
对象实例是程序中重要的数据结构,需要能够方便地引用和操作。
类型指针是一种指向对象类型的指针,它可以在程序中引用和操作对象类型。
指向对象类型,方便程序引用和操作对象。
对象类型是程序中重要的数据结构,需要能够方便地引用和操作。
对象引用是一种指向对象的引用,它可以在程序中引用和操作对象。
引用和操作对象。
对象是程序中重要的数据结构,需要能够方便地引用和操作。
对象类型数据定义是指对象的类型和属性的定义,包括对象的类名、实例变量、方法等。
定义对象的类型和属性,方便程序操作和管理对象。
对象是程序中重要的数据结构,需要定义其类型和属性。
方法区是Java堆中的一部分,主要用于存储已经被加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。在Java虚拟机的内存区域划分中,方法区位于堆内存的逻辑上方,是一块单独的内存区域。方法区的构成主要包括以下几个方面:
方法区主要用于存储已经被加载的类的信息,包括类的基本信息(如类名、父类名、接口、字段、方法等)、类的访问修饰符、方法的访问权限等。这些信息可以通过Java反射机制来访问和修改。
方法区还包括一个运行时常量池,用于存储类文件中的常量信息,包括字符串、数字、方法、字段等。在类加载时,Java虚拟机会将常量池中的常量加载到运行时常量池中,并进行解析和替换,以便于程序在运行时使用。
方法区还用于存储类的静态变量,这些变量在类加载时被初始化,并一直保存在方法区中,直到程序结束或类被卸载。静态变量可以被所有实例共享,因此它们通常用于保存和管理全局数据。
方法区还包括即时编译器编译后的代码,用于提高程序的性能和效率。即时编译器会将Java字节码编译成本地机器码,并保存在方法区中,以供程序调用。
方法描述符描述了方法的参数类型和返回值类型等信息。Java虚拟机通过方法描述符来确定方法的参数和返回值类型,以便于方法的调用和执行。
动态代理类的字节码、代理类的接口列表、代理类的方法调用处理器等。
类加载器、字节码增强器、类修改器等。
Java虚拟机内部的异常处理器、线程调度器、内存管理器等
总的来说,方法区是Java虚拟机中非常重要的一部分,它包括了类的信息、常量、静态变量、即时编译器编译后的代码、方法描述符、动态代理信息、字节码增强信息和Java虚拟机内部运行信息等内容。了解方法区的构成和作用,有助于我们更好地理解Java虚拟机的内部运行机制。