1、程序计数器
程序计数器(Program Counter Register)是在jvm内存模型中最小块线程私有的内存区域,生命周期与线程相同,可看作是当前线程执行字节码的行号指示器。是 JVM 中唯一一个不会出现 OOM(OutOfMemeryError)的区域。
- 如果线程执行的是一个 Java 方法,计数器记录的是正在执行的虚拟机字节码指令的地址;
- 如果执行的是一个 Native 方法,则计数器值为空。
程序计数器的作用:
- 字节码解释器通过改变程序计数器的值来选取下一条需要执行的字节码指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
程序计数器的特点
- 是一块较小的存储空间 线程私有。
- 每条线程都有一个程序计数器。
- 是唯一一个不会出现OutOfMemoryError的内存区域。
- 生命周期随着线程的创建而创建,随着线程的结束而死亡
2、方法区
Java虚拟机规范中定义方法区是堆的一个逻辑部分。
方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
方法区的特点:
- 线程共享 ,方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区。
- 永久代 ,方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为老年代。
- 内存回收效率低 ,方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效。对方法区的内存回收的主要目标是:对常量池的回收和对类型的卸载。
- Java虚拟机规范对方法区的要求比较宽松。和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。
3.堆
java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。
堆的特点
- 线程共享 整个Java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。
- 在虚拟机启动时创建垃圾回收的主要场所。 可以进一步细分为:新生代、老年代。 新生代又可被分为:Eden、From Survior、To Survior。 不同的区域存放具有不同生命周期的对象。这样可以根据不同的区域使用不同的垃圾回收算法,从而更具有针对性,从而更高效。
- 堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的,因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError。
4、虚拟机栈
虚拟机栈(Virtual Machine Stack)是线程私有的内存区域,生命周期与线程相同,描述着Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表(包括参数)、操作数栈、动态链接、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
虚拟机栈特点- 线程私有
- 生命周期与线程相同
栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区被组织为以一个字长为单位、从0开始计数的数组,和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的,可以看作为临时数据的存储区域。除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。
局部变量表: 存放了编译器可知的各种基本数据类型、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。
5、本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈作用类似,也是线程私有的内存区域,区别在于运行的是本地方法(Native Method)。
本地方法,即非Java语言实现的方法,比如C,使用本地方法可以扩充Java没有的语言特性。