java虚拟机的内存空间总共分成5个部分:
- 本地方法栈(Native Method Stacks)
- 程序计数器(Program Counter Register)
- 虚拟机栈(JVM Stacks)
- 堆区(Heap)
- 方法区(Method Area)
1.1 Program Counter Register
(1)定义
程序计数器是一块较小的内存空间,可以把它看作当前线程正在执行的字节码的信号指示器。程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。
注:如果当前线程正在执行的是一个本地方法,那么此时程序计数器为空。
(2)PCR的作用
- 字节码解释器通过改变程序计数器来依次执行程序指令,从而实现代码的流程控制,如代码的顺序执行、循环、选择、异常处理等
- 在多线程情况下,程序计数器用于记录当前线程执行的位置,从而都能在线程被切换回来时能够知道上一次执行的位置,然后接着上一次的执行位置继续执行。
(3)PCR的特点
JVM
中内存较小的一块区间- 是
JVM
中唯一一个不抛出OutOfMeneryError
异常的区域 - 线程私有,每一个线程都有一个程序计数器
- 生命周期会随着线程的创建而创建,随着线程的死亡而死亡
1.2 JVM Stacks
(1)定义
java
的虚拟机栈是描述java
方法运行过程的内存模型。
java
虚拟机栈会为java
中每一个即将运行的方法分配一个”栈帧“空间,用于存放该方法运行过程所需要的一些信息,包括局部变量、动态链接、操作数栈、方法出口信息等。
当方法执行完毕之后,“栈帧”中的信息出栈,且释放内存
注意:人们常说,Java的内存空间分为“栈”和“堆”,栈中存放局部变量,堆中存放对象。 这句话不完全正确!这里的“堆”可以这么理解,但这里的“栈”只代表了Java虚拟机栈中的局部变量表部分。真正的Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
局部变量表和栈帧是两个概念。
(2)JVM Stacks的特点
局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建,而且,局部变量表的大小在编译时期就确定下来了,在创建的时候,只需要为其分配内存即可。且,局部变量表的大小该方法执行过程中时不能被改变的。
JVM Stacks会抛出两种异常
$StackOverFlowError$
若虚拟机栈的大小不支持动态扩展,那么当前的虚拟机栈就是一个固定的大小,如果当前线程在请求虚拟机栈的深度超多虚拟机栈的最大深度时,就会抛出$StackOverFlowError$异常
$OutOfMeneryError$
如果虚拟机栈的大小支持动态扩展,那么当前线程在请求虚拟机栈的深度超多当前的虚拟机栈的最大深度时,那么虚拟机栈会自动进行扩容,当不断扩展直到物理空间不够时,就会抛出$OutOfMeneryError$异常
虚拟机栈是每个线程私有的,生命周期随着线程的创建而创建,随着随着线程的死亡而死亡
1.3 Native Method Stacks
(1)定义
- 本地方法栈和java的虚拟机栈类似,只不过本地方法栈是本地方法运行的内存模型
- 本地方法在即将运行时,本地方法栈也会为该本地方法创建一个栈帧,栈帧中存放该方法的一些局部变量、动态链接、操作数栈、方法出口信息等
- 在本地方法执行完毕之后,本地方法栈中的栈帧会出栈,并释放内存
- 同样也会抛出连个异常,
StackOverFlowError
和OutOfMeneryError
1.4 Heap
(1)定义
堆区是存放对象的内存区间,java中,几乎所有的对象都存放在内存中。
(2)特点
- 整个java虚拟机中只有一个堆,所有的线程都都访问同一个堆
- 堆区在JVM启动时被创建
- 堆区也是JVM中垃圾回收的主要场所
- 堆区中不同的区域会存放不同类型的对象,这样可以针对不同的区域使用不同的垃圾回收算法,这样可以让JVM更有针对性,更高效运行
- 堆的大小一般都是可扩展的(现在的主流的JVM都是支持可扩展的),当在使用过程中,堆区不断申请空间,且物理空间不足时,就会抛出
OutOfMeneryError
异常
1.5 Method Area
(1)定义
java
虚拟机规范中规定方法区是堆(Heap
)区中的一个逻辑部分
方法区中存放已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等
(2)特点
所有线程共享
因为方法区是堆区的一个逻辑部分,所以整个
JVM
中只有一个方法区永久代
因为方法区需要在
JVM
长期存在,而且它有时堆的一个逻辑部分,所以使用堆的划分方法,把方法区成为老年代内存回收效率很低
因为方法区中的对象一般都是长期存在,所以就算是对方法区进行垃圾回收,无效的对象也很少。对方法区的内存回收,主要是针对方法区中的常量进行回收,以及对类的卸载。
方法区同样支持可扩展和不可扩展,还可以设定为不实现垃圾回收。
(3)常量区
上面说到,方法区中存放的信息包括JVM
加载的类信息、常量、静态变量、即时编译器编译后的代码等,而常量信息则存放在方法区中的运行时常量池中。
在java
编程中,一般声明常量使用public static final
进行声明,这个类被编译之后转换成class
文件,这个类的所有信息都存储在这个class
文件中
当这个类被加载后,这个class
文件中的常量信息就存放在方法区中的运行时常量池中,而且,在运行期间,还可以继续向运行时常量池中添加新的常量,如:String类的intern()方法就能在运行期间向常量池中添加字符串常量。
当运行时常量池中的某些常量没有再被任何其他的变量或者对象引用时,那么在垃圾回收期间,这些常量就会被当成垃圾被回收掉。
1.6 直接内存
直接内存是除Java虚拟机之外的内存,但也有可能被Java使用。
在NIO中引入了一种基于通道和缓冲的IO方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需先将外面内存中的数据复制到堆中再操作,从而提升了数据操作的效率。
直接内存的大小不受Java虚拟机控制,但既然是内存,当内存不足时就会抛出OOM异常。
1.7 总结
Java
虚拟机的内存模型中一共有两个“栈”,分别是:Java
虚拟机栈和本地方法栈。两个“栈”的功能类似,都是方法运行过程的内存模型。并且两个“栈”内部构造相同,都是线程私有。
只不过Java
虚拟机栈描述的是Java
方法运行过程的内存模型,而本地方法栈是描述Java本地方法运行过程的内存模型。Java
虚拟机的内存模型中一共有两个“堆”,一个是原本的堆,一个是方法区。方法区本质上是属于堆的一个逻辑部分。堆中存放对象,方法区中存放类信息、常量、静态变量、即时编译器编译的代码。
堆是
Java
虚拟机中最大的一块内存区域,也是垃圾收集器主要的工作区域。程序计数器、
Java
虚拟机栈、本地方法栈是线程私有的,即每个线程都拥有各自的程序计数器、Java
虚拟机栈、本地方法区。并且他们的生命周期和所属的线程一样。而堆、方法区是线程共享的,在Java
虚拟机中只有一个堆、一个方法栈。并在JVM
启动的时候就创建,JVM
停止才销毁。
写在最后
欢迎大家关注鄙人的公众号【麦田里的守望者zhg】,让我们一起成长,谢谢。