JVM内存划分
自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇
本文作者: Jeffrey
本文链接: https://www.bytelife.net/articles/55127.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
Java虚拟机内存规范所管理的内存包括如下运行时数据区域:程序计数器、虚拟机栈、本地方法栈、Java堆、方法区等。
程序计数器(Program Counter Register)
可以看做是当前线程所执行的字节码行号指示器,每条线程都有一个独立的程序计数器(线程私有),此内存区是Java虚拟机规范中唯一没有任何OutOfMemoryError情况的区域。
Java虚拟机栈(Java Virtual Machine Stack)
线程私有,描述Java方法执行的内存模型:每个方法执行时,都会创建一个栈帧用于存放局部变量表、操作数栈、动态链接、方法出口等信息。既每一个方法的执行到完成的过程,对应着栈帧的入栈和出栈的过程。
局部变量表
局部变量表存放了编译器可知的各种基本数据类型、对象引用和returnAdress类型。
- 其中double和long类型的数据会占用2个局部变量空间(slot),其余的类型均占用1个。
- 局部变量表的空间在编译器即完成分配,大小完全确定,方法运行期间不会改变局部变量表的大小。
两种异常情况
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常。
如果虚拟机栈可以动态扩展(大部分Java虚拟机都可以),如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
本地方法栈(Native Method Stack)
与虚拟机栈的发挥作用类似,区别如下:
- 虚拟机栈为虚拟机执行Java方法(字节码)服务。
- 本地方发栈为虚拟机使用到的Native方法服务。
本地方法栈在不同的虚拟机有不同的实现方式,甚至Sun HotSpot虚拟机将本地方法栈和虚拟机栈合二为一。
本地方法栈同样会出现虚拟机栈存在的两种异常情况。
Java堆(Java Heap)
线程共享,在虚拟机启动时创建,唯一目的是存放对象实例。几乎所有的对象实例都在堆上分配。Java堆是GC的主要区域,因此也常称为“GC堆”。
- 从垃圾回收的角度看,由于现在的GC基本都采用分代的收集算法,因此还可以将Java堆细分为:新生代和老年代。再细致划分的话,有:Eden空间、From Survivor空间、To Suvivor空间。
- 从内存分配的角度看,线程共享的Java堆还可能划分成多个线程私有的分配缓冲区(TLAB)。
但无论如何划分,无论哪个区域,Java堆中存储的都是对象实例,进一步划分是为了更好的回收和分配内存。
Java堆要求逻辑上连续,但物理上可以不连续。主流虚拟机在Java堆的实现上大多采用可以扩展(通过-Xms和-Xmx控制)的实现。
如果Java堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
方法区(Method Area)
线程共享,用于存储已被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。Java虚拟机规范将其描述为堆的一个逻辑部分,但它却有个别名叫做Non-Heap(非堆),目的将其与Java堆分开。
- 对于HotSpot虚拟机来说,通常将方法区成为“永久代”(Permanent Generation),但这种描述并不准确,只是因为HotSpot的设计团队将GC分代算法扩展到了方法区,目的是省去专门为方法区编写内存管理代码的工作。对于其它虚拟机来说是不存在永久代的概念的。通常在方法区发生的GC是比较少的,这区域的主要目标是针对常量池的回收和对类型的卸载。
- HotSpot团队官方发布的路线图信息,现在也有放弃永久代并逐步改为Native Memory来实现方法区的规划。在JDK1.7的HotSpot中,已经把原本放在永久代中的字符串常量池移出。
当方法区无法满足内存的分配需求时,将会抛出OutOfMemoryError异常。
运行时常量池(Runtime Constant Pool)
运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息以外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放。
- 对于运行时常量池,Java虚拟机规范中没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。不过,一般来说,除了存储Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
- 运行时常量池相对于Class文件常量池的另外一个重要特性是具备动态性。Java语言不要求敞亮一定要在编译期产生,也就是说并非一定要预置入Class文件中常量池的内容才能进入运行时常量池,运行期间也可能将新的敞亮放入池中,这种特性被开发人员利用的比较多的便是String类的intern()方法。
当运行时常量池无法满足内存的分配需求,将会抛出OutOfMemoryError异常。
直接内存(Direct Memory)
直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中的内存区域,但却被频繁使用,也有可能导致OutOfMemoryError异常。
在NIO中,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,可以利用Native函数库直接分配堆外内存,通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,避免了在Java堆和Native堆中来回复制数据。
直接内存不受到Java堆大小的限制,但是仍然受到本机RAM和SWAP区(或分页文件)大小以及处理器寻址空间的限制。服务器管理员在配置虚拟机参数时,常常忽略直接内存而设置-Xmx等信息,终而导致各个内存区的总大小大于物理内存限制,从而导致动态扩展时出现的OutOfMemoryError异常。