JVM的内存区域及其使命
JAVA内存区域及其使命
1. 前言
在JAVA虚拟机中,虚拟机的内存管理机制帮助我们分配和释放空间,其拥有内存控制的权力,大大减少了程序员在开发过程中出现的内存泄漏和内存溢出问题。但是一旦出现问题,我们就需要从虚拟机机制入手。
2. 数据区域
JAVA虚拟机在执行的时候会将内存划分为若干个区域,他们有着各自不同的使命。
1).程序计数器
程序计数器占有少量的内存,其主要用来指示当前所执行的字节码位置,一切的基础逻辑功能都需要计数器来指示下一条执行的指令。
若当前线程正在执行JAVA方法,则计数器中存放的是字节码指令的地址;若执行的是Native方法,则计数器值为Undefined。
计数器为每个线程提供指示服务,所以程序计数器是每个线程所独有的区域。JAVA虚拟机并没有规定该区域的OutOfMem
oryError异常。
2).本地方法栈
本地方法也是线程私有的,生命周期与线程相同,主要用来描述执行Native方法的内存模型,每个方法执行时都会创建一个栈帧,栈帧用于存放局部变量表、操作数栈、动态链接和方法出口等。每一个方法的执行都伴随这一次入栈出栈。
本地方法栈可以抛出StackOverflowError(请求超出栈深度)和OutOfMemoryError(无法申请足够内存)异常。
3).虚拟机栈
这是JAVA中比较重要的一个区域,同本地方法栈一样,只不过执行的是JAVA方法。可以抛出StackOverflowError(请求超出栈深度)和OutOfMemoryError(无法申请足够内存)异常。
4).方法区
方法区是一个线程共享区域,用于存放被虚拟机加载的类信息、常亮、静态变量,可以看做编译后的代码数据。我们需要将方法区与堆分开,不可混合。在HotSpot虚拟机中,使用永久代来实现了方法区,可以省去方法区的内存管理工作。但在JDK1.7以后将放在永久代中的字符串常量池移出。方法区不需要连续的内存,可固定内存大小也可以实时扩展,并且可以不做垃圾收集。因为该区域的垃圾收集比较少见,但是并不是说该部分的数据不需要回收。
当程序想要new一个对象时,会首先确认方法区中是否有一个类的符号引用,并且这个类必须被加载、解析、初始化,一切准备就绪后才进入堆中操作。
5).堆
堆是所有线程共有的存储空间,这里是垃圾回收的最前线也是影响性能的关键。JAVA堆的唯一目的就是存放对象实例,但是并不是所有的对象都在堆上被分配。
受到垃圾收集机制的影响,JAVA堆被分为新生代和老年代;新生代用来分配生命周期较短的对象实例,老年代则相反;在分代的基础上使用不同的收集算法可以使得垃圾收集有更短的停顿或者更大的吞吐量;新生代一般使用“复制清除算法”,老年代则使用“标记整理”或者“标记清除”算法,这里我不对算法做具体的讲解。
JAVA堆可能为不同的线程划分出属于它自己的一块内存区域,但是这个区域不是绝对的,区域的划分可以有助于垃圾回收和对象存储。JAVA堆存储方式有“指针碰撞”和“空闲列表”两种。”指针碰撞“指存储空间被指针划分为已使用和未使用两部分,分配空间时只需要移动指针即可;“空闲列表”则用在存储空间不规则、存在碎片的情况下。分配空间之前需要知道对象的大小,这也是基本类型需要指定大小的原因。
JAVA堆可以不用是物理连续的空间,只要逻辑连续即可;这样的设计同一些数据类型一样实现了动态扩展的可能。