一、为什么需要了解虚拟机的内存分布?
Java程序员和C++、C程序员在内存管理领域是不一样的,对java程序员来说,java程序员不需要关注内存管理,就能编写代码,运行程序,内存完全托管给虚拟机,但是一旦出现内存泄露和溢出问题,如果不了解虚拟机内存分布,怎么使用内存,将无法快速准确的定位问题发生区域,并且了解jvm内存管理,也能写出更合理利用资源的代码。
二、虚拟机运行时内存分布
2.1程序计数器
线程私有,较小的一块内存区域,它可以当做当前线程所执行的字节码的行号指示器;
注意: 唯一一个在java虚拟机规范中,没有规定任何OutOfMemoryError情况的区域。
2.2Java虚拟机栈
线程私有,生命周期和线程相同。虚拟机栈描述java方法执行的内存模型:每个方法在执行的同时,都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用开始到结束,就是对于一个栈帧在虚拟机栈中入栈到出栈的过程。
常规的内存分为“堆“,“栈”,是比较粗糙的,java虚拟机内存划分比这复杂,并且常规描述的栈,只是可能指栈中局部变量表部分。局部变量表存放编译期可知的8种基本数据类型和对象引用,其数据类型为long,double占2个局部变量空间(slot),其他数据类型占1个局部变量空间。
byte b; 1字节
short s; 2字节
int i; 4字节
long l; 8字节
char c; 2字节(C语言中是1字节)
float f; 4字节
double d; 8字节
boolean bool; false/true 1字节
对象引用 4 字节
注意:栈中,虚拟机规范定义了两种异常;
a.StackOverFlowError 栈溢出 如果线程请求的栈深度大于虚拟机所允许的深度,抛出栈溢出
b.OutOfMemoryError 内存溢出,如果虚拟机栈可以动态扩展,扩展时无法申请到足够内存,抛出内存溢出
2.3本地方法栈
本地方法栈和虚拟机栈作用类似,虚拟机栈为虚拟机执行java方法服务,本地方法栈为虚拟机使用到Native方法服务。
注意:本地方法栈中,虚拟机规范定义了两种异常;
a.StackOverFlowError 栈溢出 如果线程请求的栈深度大于虚拟机所允许的深度,抛出栈溢出
b.OutOfMemoryError 内存溢出,如果虚拟机栈可以动态扩展,扩展时无法申请到足够内存,抛出内存溢出
2.4 Java堆
线程共享的一块内存区域,是java虚拟机所管理的内存中最大的一块,在虚拟机启动时候创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存(栈上分配、标量替换优化技术,就是特例)。
Java堆是垃圾收集器管理的主要区域。从内存回收的角度来看,现在收集器基本上都采用分代收集算法,堆也可以分为:新生代和老年代;再细致一些,新生代分为Eden空间、From Surivor空间、To Surivor空间;从内存分配角度来看,线程共享的Java堆可能划分多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)。不论怎么划分,都是存储对象实例,进一步划分的目前是为了更好回收内存,或者说是更快的分配内存。
注意:如果堆中没有内存完成实例分配,并且堆无法扩展时候,会抛出OutOfMemoryError异常;
2.5 方法区
线程共享的内存区域,存储已被迅疾加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在HotSpot虚拟机上,开发人员也习惯称它为“永久代”,因为HotSpot设计团队,将GC分代收集扩展到了方法区,或者使用永久代来实现方法区。
HotSpot虚拟机,根据官方发布的路线图信息显示,有放弃永久代逐步改为采用Native Memory来实现方法区的规划了,在目前发布JDK1.7中,已经将字符串常量池移出永久代了。JDK1.8使用本地内存实现的方法区MetaSpace,受限于机器本身内存。
此区域垃圾回收比较少,回收主要目标是常量池回收和类型的卸载。
注意:当方法区无法满足内存分配需求时候,将抛出OutOfMemoryError异常。
2.6运行时常量池
运行时常量池,是方法区的一部分。Class文件除了有累的版本、字段、方法、接口等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。Java虚拟机规范要求Class文件常量池的格式有严格规定,不同数据,需要不同字节大小,但是对运行时常量池没有做细节要求。运行时常量池,除了存储Class文件中描述的符号引用(类 、接口全限定名、字段名称描述、方法名称描述)外,还会把翻译出来的直接引用也存储在运行时常量池中,在运行期间还可以通String的intern方法,将常量放入池中。
注意:运行时常量池属于方法区一部分,所以大小受到方法区内存的限制,当常量池无法再申请到内存时候,会抛出OutOfMemoryError异常。
2.7直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义内存区域。
NIO类,引入一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能再一些场景中显著提高性能,避免java堆和Native堆中复制数据。
注意: 直接内存受限于机器内存,可能各个区域内存总和大于物理内存限制,从而导致动态扩展时候,出现OutOfMemoryError异常。