JVM运行时数据区
JVM内存模型 (5种): 栈、本地方法栈、程序计数器、堆、方法区
线程独占:栈,本地方法栈,程序计数器
每个线程都会有它独立的空间,随线程生命周期而创建和销毁
线程共享:堆,方法区
所有线程能访问这块内存数据,随虚拟机或者GC而创建和销毁
1.栈:(JVM Stack)又称方法栈
JVM中的虚拟机栈是描述Java方法执行的内存区域,属【线程私有】
栈中的元素用于支持虚拟机进行方法调用,每个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程。
线程私有的
,
线程执行方法是都会创建一个栈阵
,
用来存储局部变量表
,
操作栈
,
动态链接
,
方法出口等信息.
调用方法时执行入栈
,
方法返回式执行出栈
.
栈是
管理JAVA方法执行的内存模型。每个方法执行时都会创建一个桢栈来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss
参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛stackOverflflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。
2.本地方法栈 (Native Method Stack)
与栈类似
,
也是用来保存执行方法的信息
.
执行
Java
方法是使用栈
,
执行
Native
方法时使用本地方法栈
.
3.程序计数器 (Program Counter Register )
一块较小的内存空间,【线程私有】。每条线程都有一个独立的程序计数器。
当同时进行的线程数超过CPU
数或其内核数时,就要通过时间片轮询分派
CPU
的时间资源,不免发生线 程切换。这时,每个线程就需要一个属于自己的计数器来记录下一条要运行的指令。如果执行的是JAVA 方法,计数器记录正在执行的java
字节码地址,如果执行的是
native
方法,则计数器为空。
保存着当前线程执行的字节码位置,
每个线程工作时都有独立的计数器
,
只为执行
Java
方法服务
,
执行Native方法时
,
程序计数器为空
.
程序计数器(后文简称为 PCR)有两个作用:
- 字节码解释器通过改变程序计数器依次读取指令,实现代码的流程控制,如:顺序执行、选择、循环、异常处理
- 多线程情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪了
4.堆 (Java Heap)
JVM内存管理最大的一块
,
对被线程共享
,
目的是存放对象的实例
,
几乎所欲的对象实例都会放在这里
,
当堆没有可用空间时,
会抛出
OOM
异常
.
根据对象的存活周期不同
,JVM
把对象进行分代管理
,
由垃圾回收器进行垃圾的回收管理
JVM内存管理最大的一块
,
被线程共享, 存放对象实例和数组,是垃圾回收的主要区域,根据对象的存活周期不同
,JVM
把对象进行分代管理,分为新生代和老年代。刚创建的对象在新生代的Eden区中,经过GC后进入新生代的S0
区中,再经过
GC
进入新生代的
S1
区中,
15
次
GC
后仍存在就进入老年代。这是按照一种回收机制进行划分的,不是固定的。若堆的空间不够实例分配,则OutOfMemoryError。
5.方法区:
又称非堆区, 线程共享的,用于存放被虚拟机加载的类的元数据信息,如常量、静态变量和即时编译器编译后的代码。若要分代,算是永久代(老年代),以前类大多“static”的,很少被卸载或收集,现回收废弃常量和无用的类。其中运行时常量池存放编译生成的各种常量。(如果hotspot
虚拟机确定一个类的定义信息 不会被使用,也会将其回收。回收的基本条件至少有:所有该类的实例被回收,而且装载该类的 ClassLoader被回收)
1.7
的永久代和1.8
的元空间都是方法区的一种实现
特点:
- 线程共享
方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的.整个虚拟机中只有一个方法区.
- 永久代
方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为永久代.
- 内存回收效率低
Java虚拟机规范对方法区的要求比较宽松,可以不实现垃圾收集.
方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效.
对方法区的内存回收的主要目标是:对常量池的回收和对类型的卸载
和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。
当方法区内存空间无法满足内存分配需求时,将抛出OutOfMemoryError异常.