前言
Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对数据进行了不同空间的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
具体划分为如下5个内存空间:
虚拟机栈、堆、方法区、程序计数器、本地方法栈
1、程序计数器
每个线程拥有一个PC寄存器
在线程创建时创建
指向下一条指令的地址
执行本地方法时,PC的值为undefined
此区域是内存中唯一一块没有规定任何OutOfMemoryError(内存溢出)情况的区域,为什么?因为我们不需要操作该区域,该区域是内部维护的。
2、虚拟机栈
线程私有,生命周期和线程相同。
每个方法被调用的时候都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机中从入栈到出栈的过程。
在Java虚拟机规范中,对这个区域规定了两种异常情况:
(1)如果线程请求的栈深度太深,超出了虚拟机所允许的深度,就会出现StackOverFlowError(比如无限递归。因为每一层栈帧都占用一定空间,而 Xss 规定了栈的最大空间,超出这个值就会报错)
(2)虚拟机栈可以动态扩展,如果扩展到无法申请足够的内存空间,会出现OOM
3、本地方法栈
是为本地的native方法服务的,其他的都和虚拟机栈一样。
4、堆
线程共享的一块区域,用来存放对象实例的(由于现在有了逃逸分析技术,也可以将对象分配在栈上),该区域是垃圾回收的主要区域,垃圾回收主要是分代回收,有年轻代和老年代,堆可以是物理上不连续的区域,只要逻辑上连续即可。在堆中分配内存的方法有碰撞指针(前提是区域绝对规整,注意多线程同步问题,可以采用CAS原理加失败重试实现或者本地线程分配缓冲)和空闲列表(不是规整的内存,就是有一个表记录空闲的内存,然后分配后,从该表中去除),堆空间不足时会出现OutOfMemoryError异常。
逃逸分析技术是当虚拟机采用编译器执行代码的一种优化方案。
逃逸分析主要是分析对象的作用域,如果一个变量的作用域在方法体内,并且没有将该变量赋给方法体外的变量,那么这个对象就没有发生逃逸现象,为了提高性能,在栈上分配该对象的内存,当栈帧从Java虚拟机栈中弹出,就自动销毁这个对象。减小垃圾回收器压力。
5、方法区
线程共享的区域,存储JVM加载的类信息(类的版本,字段,方法,接口),常量,静态变量以及即时编译后的代码等数据。方法区还有一块运行时常量池,class文件中的常量池在类加载后就被放入运行时常量池,运行时常量池相对于class文件的常量池具有动态性,可以在运行期间通过intern将常量放入运行时常量池中,方法区空间不足时会抛出OutOfMemoryError异常。
特例
除了Java虚拟机规定的这几块区域以外呢,还存在一个堆外内存,即直接内存,直接内存能减少IO时的内存复制了,实现零拷贝,而且没有GC,减少了垃圾回收的工作,加快了复制的速度,该区域也难以控制,如果内存泄漏很难排查。