JVM内存区域划分
Java虚拟机内存区域可以分为两部分:线程私有区域,线程共享区域。
线程私有区域
1.程序计数器
程序计数器,记录当前正在执行的字节码的地址,通俗而言就是记录当前执行到的的字节码的行号,如果当前正在执行的是一个本地方法的话,该值为空(Undifined)
程序计数器的作用
Java虚拟机的多线程是通过轮流切换CPU时间片的方式来实现的,任意时刻,一个单核处理器只能执行一个线程中的指令。为了在发生线程切换后,CPU能从正确的位置继续执行,因此需要程序计数器。程序计数器只能记录所在线程的执行行号,每个线程都有属于自己的程序计数器,因此属于线程私有的。
可能的异常或者错误
因为程序计数器只记录字节码的行号,因此所占内存空间特别小,不会发生OOM和StackOverflow。(也是唯一一块不会抛出OOM的内存区域)。
2.Java虚拟机栈
Java虚拟机栈,描述Java方法执行的内存模型,方法调用时,都会创建一个栈帧,栈帧中存储方法的局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。
局部变量表
局部变量表存储编译器可知的数据类型(8大基本数据类型和对象引用),因为Java是一门跨平台的语言,因此这些数据类型的大小在编译器间就是已知的,因此局部变量表的内存空间在编译期间就已经分配完毕,且在运行期间不会再改变。
可能的异常或者错误
当方法调用的栈深度超过虚拟机运行的栈深度时(没有出口的方法递归调用),就会抛出StackOverflow错误。一般而言栈的深度是可以扩展的,当扩展时无法再申请到内存时就会抛出OutOfMemroy错误。那么到底抛出哪个错误呢?在单线程情况下抛出Stackoverflow,多线程下抛出OOM。
3.本地方法栈
本地方法栈的作用与Java虚拟机栈的作用基本是一致的只是,本地方法栈上执行的是Native方法,而虚拟机栈上执行的是Java方法而言。而且堆本地方法栈的使用并没有严格规定,因此有的虚拟机直接将两部分合二为一。
可能的异常或者错误
与虚拟机栈一样该区域也可能抛出StackOverflow和OOM,且原因和本地方法栈是一样的。
线程共享区域
1.Java堆
Java堆在虚拟机启动时创建,是Java虚拟机管理的内存的最大的一块。Java堆是垃圾回收器管理的主要区域,因此也成为GC堆。Java堆上存放对象实例数据,和数组,但并非所有的对象实例和数组都在堆上分配。Java堆在逻辑上是连续的,在物理地址上一般是不连续的。
可能的异常或者错误
当堆中的对象所占内存达到最大值,这时候再创建新对象就会抛出OOM。堆可以通过-Xmx和-Xms来控制相应的大小。
2.方法区
方法区也是各个线程共享的区域,该区域主要存放加载的类信息,常量,静态变量,编译后的代码等。方法区也成为永久代,在JDK8更名为元空间。
永久代并不意味着数据进入方法区就永久存在,此区域的内存回收主要是针对常量池的回收以及对类型的卸载,其中对类型的卸载条件要苛刻很多。
可能的异常或者错误
当方法区无法满足内存需求时就会抛出OOM错误。
3.运行时常量池(方法区的一部分)
运行时常量池也是线程共享的内存区域,该区域保存字面量和符号引用。
字面常量:
- 字符串(如 “abc”)
- final修饰的变量(final int a = 10;)
- 基本类型的值等信息(10,true,null等)。
符号引用:
- 类和接口的全限定名;
- 字段即属性的名称和描述符
- 方法的名称和描述符
可能抛出的错误
运行时常量池是是方法区的一部分,因此在无法满足内存需求时,也会抛出OOM。