您的位置:首页 > 编程语言 > Java开发

JVM内存区域(基于jdk1.7)

2018-07-13 17:33 399 查看


一 程序计数器

程序计数器是一块较小的内存空间,可以看做当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅仅是概念模型,各种虚拟机可能通过一些更加高效的方式实现),字节码解释器工作时就是通过改变这个计数器的值进行下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。各个线程之间的计数器互不影响,独立存储,为线程私有。

如果线程正在执行的是一个java方法,这个计数器记录的是正在执行虚拟机字节码指令的地址,如果是native方法,此计数器则为空(Undefined)。此内存区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

二 虚拟机栈

虚拟机栈也是线程私有的,每个方法执行的同时会创建一个栈帧用于存储局部变量表、操作数栈(保存计算过程的中间结果)、动态链接、方法出口等信息。局部变量表存放着编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用类型(所以取对象也是通过此引用获取)。进入一个方法中时,需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表大小。

如果线程请求的栈深度大于虚拟机允许的深度,将会抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,将会抛出OutOfMemoryError异常。个人感觉这两个异常只是表述的角度不一样,实际上是指代同一个东西。

三 本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是十分相似的。唯一的区别是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务。

四 堆

所有的对象以及数组都要在堆上分配,但是随着JIT编译器的发展和逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生。

java堆还可以细分为:新生代和老年代,再细致一点的有Eden空间、From Survivor空间、To Survivor空间等,同时线程共享的堆还可以划分多个线程私有的分配缓冲区。进一步划分的目的是更好的进行内存分配和回收内存。

五 方法区

方法区主要用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。需要注意的是"永久区"和方法区并不等价,仅仅是因为HotSpot虚拟机团队把gc分代收集扩展至方法区,或者说用永久代来实现方法区而已,在目前的jdk1.7的HotSpot,已经把原本在永久代的字符串常量池移出。jdk1.8已经把永久代区移出,取而代之的是元数据区。可以使用参数-XX:MaxMetaspaceSize指定。

六 运行时常量池

运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。运行期间也可以把新的常量放入池中,像String类的intern()方法便是其中一个应用。常量池的内存受到方法区内存的限制,无法申请到内存时会抛出OutOfMemoryError异常

七 直接内存

直接内存(Direct Memory)并不是虚拟机运行时的数据区的一部分,但是这部分内存也被频繁使用,也可能导致OutOfMemoryError异常出现。比如jdk1.4引进的NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个储存在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了java堆和native堆中来回复制数据。

配置虚拟机参数时,往往是根据内存配置-Xmx等参数而忘了直接内存,使得各个区域内存总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java JDK