JVM内存划分:
- 方法区(线程共享):存储常量、静态变量、JIT(即时编译器)编译后代码也在方法区存放
- 堆内存(线程共享):GC垃圾回收的主要场地
- 程序计算器:当前线程执行的字节码的位置指示器
- Java虚拟机栈(栈内存):保存局部变量,基本数据类型以及堆内存中对象的引用变量
- 本地方法栈(C栈):JVM提供使用native方法的服务
详细介绍
1. 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。另外,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程间计数器互不影响,独立存储,我们称这类内存区域为线程私有的内存。
主要作用:
(1)字节码解释器通过改变线程计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择循环、异常处理
(2)在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候,能够知道该线程上次运行到的位置。
注:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而消亡。
2. Java虚拟机栈
与程序计数器一样,Java虚拟机栈是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的结束而消亡。描述的是Java方法执行的内存模型。
Java内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是虚拟机栈,或者说是虚拟机栈中的局部变量表部分。
(实际上,Java虚拟机栈由一个个栈帧组成,而每个栈帧都包含:局部变量表、操作数栈、动态链接(XXX.dll)、方法出口信息)
java虚拟机栈会出现两种异常:StackOverFlowError(栈溢出异常)和OutOfMemoryError(OOM,内存溢出异常)
StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,当线程请求栈的深度超过当前Java虚拟机栈的最大深度时,就抛出StackOverFlowError异常。
OutOfMemoryError:若Java虚拟机栈的内存大小允许动态扩展,当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
3. 本地方法栈(和虚拟机栈所发挥的作用非常相似)
区别:虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则是为虚拟机使用到的native方法服务。
本地方法被执行时,在本地方法栈会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、方法出口信息。方法执行完毕后,相应的栈帧也会出栈并释放内存空间,也会出现StackOverFlowError和OutOfMemoryError两种异常。
4. Java堆
Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。Java堆是垃圾收集器管理的主要区域,因此也被成为GC堆(Garbage Collected Heap)。从垃圾回收的角度,由于出现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代;再细致划分有:Eden空间、From Survivor(S1)、To Survivor(S2)空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
永久代说明:
jdk1.6及之前:常量池分配在永久代
jdk1.7:有,但已逐步“去永久代”
jdk1.8及之后:无(java.lang.OutOfMemoryError:PermGen space,这种错误将不会出现在JDK1.8中) 在jdk1.8中移除整个永久代,取而代之的是元空间(Metaspace)区域
(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)
5. 方法区
方法区与java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java对分开来。
HotSpot虚拟机中方法去也常被成为“永久代”,本质上两者并不等价,仅仅是因为HotSpot虚拟机设计团队用永久代来实现方法区而已,这样HotSpot虚拟机的垃圾收集器就可以像管理Java堆一样管理这部分内存,但是这样会造成更容易遇到内存溢出问题。
总结:
功能 | 是否线程共享 | 生命周期 | 抛出异常 | |
程序计数器 | 当前线程执行的字节码的位置指示器 | 线程私有 | 随着线程的创建而创建,随着线程的结束而消亡 | 不会出现OutOfMemoryError异常 |
Java虚拟机栈 | 保存局部变量,基本数据类型以及堆内存中对象的引用变量 | 线程私有 | 随着线程的创建而创建,随着线程的结束而消亡 | StackOverFlowError、OutOfMemoryError |
本地方法栈 | JVM提供使用native方法的服务 | 线程私有 | 随着线程的创建而创建,随着线程的结束而消亡 | StackOverFlowError、OutOfMemoryError |
Java栈 | GC垃圾回收的主要场地 | 线程共享 | 与线程相同 | 无 |
方法区 | 储存常量、静态变量、JIT(即时编译器) 编译后代码也在方法区存放 | 线程共享 | 与线程相同 | 无 |