​ jvm在执行的过程中会把它所管理的内存划分为若干个不同的数据区域,这些数据区域中,有些依赖着用户线程的启动和结束而建立和销毁,有些则随着jvm进程的启动而创建

内存区域

jvm的运行数据区域可以分为两种:线程私有和线程共享

线程私有: 每个线程的私有数据,包括: 程序计数器、java虚拟机栈、本地方法栈

线程共享: 所有线程共享的部分,包括: Java 堆、方法区、常量池

jvm内存区域划图示

运行时数据区域

程序计数器

程序计数器是较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器,用来执行选取小一条需要执行的字节码指令,属于线程私有的内存

如果执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址,如果执行的是Native方法,这个计数器值为空

虚拟机栈

虚拟机栈,也是线程私有的,它的生命周期与线程相同,每个方法在执行时都创建一个栈帧,每个方法从调用到执行完成,对应着一个栈帧的入栈到出栈

虚拟机栈描述的是java方法执行的动态内存模型

栈帧存储的数据包括: 局部变量表,操作数栈,动态链接,方法出口等信息

在虚拟机栈中,规定了两种异常:

  1. 当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误
  2. 当虚拟机栈在扩展内存时无法申请到足够的内存,会抛出OutOfMemoryError异常

本地方法栈

线程私有,与虚拟机栈提供的功能类似,只不过执行的是本地方法,也就是用Native修饰的方法

堆是被线程共享的一块内存区域,随着jvm的启动而创建,用来存放对象实例和数组

这块区域也是GC进行垃圾回收的主要区域,当申请不到空间时会抛出 OutOfMemoryError异常

堆可以细分为新生代和老年代,新生代用来存放存活时间较短的对象,老年代用来存放存活时间较长的对象,新生代还可以细分为一个Eden区和两个Survivor;

方法区

方法区也是被线程共享的一块内存区域,用来存虚拟机中加载的类信息,常量,静态变量,即时编译器编译后的代码等数据

方法区是jvm的规范,而接下来要说的永久代和元空间正式jvm规范的一种实现

永久代(Permanent Space)

在jdk1.7时,方法区被叫做永久代(Permanent Space),由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出,当永久代出现内存溢出,会抛出java.lang.OutOfMemoryError: PermGen space异常

元空间(MetaSpace)

在jdk1.8中移除了永久代,使用元空间代替,jdk1.7中,存储在永久代的部分数据就已经转移到了堆内存或者是直接内存。但永久代仍存在于jdk1.7中,并没完全移除,譬如符号引用(Symbols)转移到了直接内存;字面量(interned strings),类的静态变量(class statics)转移到了堆内存

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的,最大内存受本地内存的限制