JVM运行时内存划分

来源:互联网 发布:java连接sqlserver代码 编辑:程序博客网 时间:2024/06/06 23:45

在Java运行时的数据区里,由JVM管理的内存区域分为下图几个模块:
这里写图片描述

其中,
程序计数器、虚拟机栈、本地方法栈 是线程私有的,每个线程都有一份。
堆、方法区 是线程共享的。

1、程序计数器(Program Counter Register)

线程私有。

程序计数器是一个较小的内存空间,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。

如果程序正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个本地(Native,由C语言编写完成)方法,这个计数器的值为空(Undefined)。

由于程序计数器只是记录当前指令的地址,所以不存在内存溢出的情况。因此,程序计数器也是所有JVM内存区域中唯一一个没有规定OutOfMemoryError情况的区域。

2、虚拟机栈(JVM Stack)

线程私有。

一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame)。栈帧中存储的有局部变量表操作数栈动态链接方法出口等信息。当方法被调用时,栈帧在虚拟机栈中入栈,当方法执行完成时,栈帧出栈。

局部变量表中存储着方法的相关局部变量,包括各种基本数据类型对象的引用返回地址等。在局部变量表中,只有long和double类型会占用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,局部变量表是在编译时就已经确定好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。

虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。

3、本地方法栈(Native Method Statck)

线程私有。

本地方法栈在作用,运行机制,异常类型等方面都与虚拟机栈相同。

唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行Native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。

4、堆(Heap)

线程共享。

在JVM所管理的内存中,堆是最大的一块,堆也是Java GC机制所管理的主要内存区域,堆由所有线程共享,在虚拟机启动时创建。

堆的存在的唯一目的就是为了存放对象实例。原则上讲,所有的对象实例以及数组都在堆上分配内存(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。

堆可以分为:新生代老年代
再细致一点可分为:Eden空间、From Survivor空间、To Survivor空间。

根据Java虚拟机规范规定,堆内存需要在逻辑上是连续的(在物理上不需要),在实现时,可以是固定大小的,也可以是可扩展的,目前主流的虚拟机都是可扩展的。

5、方法区(Method Area)

线程共享。

方法区用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量静态变量即时编译器编译后的代码等数据

  方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,方法区上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。

在HotSpot里经常被称为永久代,在Java 8里已被废除了,被元空间取代。

运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量符号引用翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。