运行时数据区域
Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在, 有些区域则是依赖用户线程的启动和结束而建立和销毁。如图
1.1 程序计数器
是一块内存比较小的空间。作用:用来标志当前线程所执行的字节码的行号指示器(即在字节码中添加编号)。在jvm中,字节码解释器工作时就是通过改变这个计数器上的值来选取下一条需要执行的字节码指令。在分支、循环、跳转、异常处理、线程恢复等功能都需要依赖计数器去完成。在多线程中, 各条线程之间的计数器互不影响,独立存储。
不会存在OutOfMemoryError情况的区域。
1.2 Java虚拟机栈
Java虚拟机栈也是线程私有的, 生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等,并且每个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
先理解什么是栈帧:是用于支持虚拟机进行方法调用和方法执行的数据结构。栈帧是基于虚拟机栈上的,一个虚拟机栈存储了多个栈帧,每个栈帧记录Java方法中局部标量表、操作数栈等信息同时也记录该方法从入栈到出栈的过程。存储结构如图
在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定,并且写入到方法表的Code属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。
局部变量表: 是一组变量值的存储空间,用于存放方法参数、局部变量和对象引用类型。在编译程序代码后就确定需要局部变量的最大容量。变量槽是局部变量表中的最小单位。一个solt可以存放(x32)boolean、byte、char、short、int、float、reference 和 returnAddress(执行字节码指令的地址);对于64位的long和double变量则需要分配两个连续的slot空间。为了节省栈空间Slot是可以重用的。不过有时也会影响垃圾回收行为。
从图一和图二中可以看出,图二中变量槽是可以重用的,变量a复用了placeholder的slot,导致placheholder的引用被删除,内存被回收。
操作数栈:常称为操作栈是一个后入先出栈。其最大的栈深度也在编译过程中就确定了并保存在Class文件的Code属性中。应用场景:在方法进行参数传递的时候是通过操作数栈进行的。 在概念模型中,两个栈帧作为虚拟机栈的元素, 相互之间是完全独立的, 但是大多数虚拟机的实现里都会做些优化处理,使得两个栈帧出现一部分重叠。让下栈帧的部分操作与上面栈帧的部分局部变量表重叠在一起,这样在进行方法调用返回时就可以共用一分部数据,而无需进行额外的参数复制传递了。
动态连接:每个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件中存放了大量的符号引用,字节码中的方法调用执行就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
方法出口:方法出口分为两种, 一种收到返回命令,正常退出;第二种执行遇到异常导致方法退出。
虚拟机栈会出现两种异常:StackOverflowError和OutOfMemoryError
1.3 本地方法栈
本地方法栈与虚拟机栈作用非常相似,其区别不过是虚拟机栈为执行Java方法(字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
Native方法:并不一定指Java中用native方法(如String.intern() :native方法),也包括其他库或者其他语言中的方法如C、C++。
本地方法栈也会出现两种异常:StackOverflowError和OutOfMemoryError
1.4 Java堆
Java堆是Java虚拟机所管理的内存中最大的一块。是被所有线程共享的一块内存区域,虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。几乎所有的对象实例都在这里分配内存,但随着jit编译器的发展会有微妙的变化。
Java heap 是垃圾收集器管理的主要区域。在Java中,堆被划分成两个不同的区域:新生代(Young)和老年代(Old)。新生代又被划分为三个区域:Eden空间、From Survivor空间、To Survivor空间。这样划分的目的是为了更好地管理内存中的对象,包括内存的分配以及回收。堆的内存模型如图(jdk1.6)
从图中可以看出:堆大小=新生代+老年代。其中堆大小可以通过参数-Xms、-Xmx来设置。
Java 堆会有OutOfMemoryError异常
1.5 方法区
方法区与Java堆一样,是各个线程共享的内存区域,但是存储的内容不同。方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。别名Non-Heap. 另外方法区并不是连续的,所以垃圾收集行为在这个区域是很少出现的。
方法区还包括一部分:常量池。常量池是用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。大小可以通过-XX:PermSize和-XX:MaxPermSize设置。
方法区会有OutOfMemoryError异常
总结:
1、区分下java栈、java堆和方法区分别存储内容:
public class Demo{
private String name ;
public Demo(String name){
this.name = name;
}
public static void main(String[] args){
Demo d = new Demo("test");
}
}
java堆 方法区 java栈
new Demo() Demo类信息 Demo d、局部变量name
Demo中的方法名 调用main方法主线程调用栈