Java内存模型
java的内存区域划分
根据《Java虚拟机规范》的规定,运行时数据区通常包括这几个部分:
程序计数器(Program Counter Register)
、
虚拟机栈(VM Stack)
、
本地方法栈(Native Method Stack)
、
方法区(Method Area)
、
堆(Heap)
。
如下图:
看到这个模型,使用堆和栈划分内存区域还是很有道理的,java虚拟机规范将方法区描述为堆的一个逻辑部分
。
这种模型和我们粗略的内存模型的对应关系为:
堆=堆+方法区
栈=虚拟机栈+本地方法栈
-
程序计数器
线程隔离,即每个线程都有自己的程序计数器,并且互不影响。分为两种情况,当线程正在执行的是一个java方法,它的作用是作为字节码的行号指示器,指向下一条需要执行的指令。当线程正在执行的是一个
Native方法
,那么它的值为空(Undefined)
。java虚拟机规范中唯一没有定义OOM(OutOfMemoryError)异常
的内存区域。
-
java虚拟机栈
线程隔离,生命周期与线程相同。它描述的是java方法执行的内存模型,每一个java方法执行的时候都会产生一个
栈帧(Stack Frame)
,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当进入一个方法时,栈帧的大小是编译器确定的,运行时不会改变其大小。当虚拟机栈不可扩展的时候,可能抛出StackOverflowError异常
,反之,可能抛出OOM异常
。
-
本地方法栈
与java虚拟机栈功能一致,只不过本地方法栈是针对
Native方法
的。同样在虚拟机规范中定义了StackOverflowError
和OOM
两种异常。
-
Java堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java虚拟机规范中的描述为:所有的对象实例以及数组都要在堆上分配。
只要是用到new关键字
创建的对象都会进入到这个区域,包括对象,数组
。
堆还能进一步划分,比如按照内存回收的角度来看,堆可以进一步划分为新生代(Eden+Survivor)
和老年代
。按照内存分配的角度来看,堆可以进一步划分为多个线程私有的分配缓存区,即TLAB(Thread Local Allocation Buffer)
。这种进一步的划分是为了更高效地回收和分配内存。java虚拟机规范中定义了OOM异常
。 -
方法区
这个区域很容易引发误会,很多人会以为方法区会存储方法中的局部变量,然而并不是。方法区与Java堆一样,是各个线程共享的内存区域,这个区域用于存储被加载的类的信息,常量,静态变量以及即时编译器编译后的代码等数据。
Java虚拟机把方法区描述为堆得一个逻辑部分
。虚拟机规范中定义了OOM异常
。还需要注意的一点是,HotSpot虚拟机中的方法区被很多人称之为“永生代”,这是因为HotSpot的开发团队将分代收集算法运用到了方法区,但是这并不是必须的。