2.1.3. 程序计数器的特点
- 是一块较小的存储
【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
空间
-
线程私有。每条线程都有一个程序计数器。
-
是唯一一个不会出现OutOfMemoryError的内存区域。
-
生命周期随着线程的创建而创建,随着线程的结束而死亡。
2.2 虚拟机栈
虚拟机栈,生命周期与线程相同,是Java方法执行的内存模型。每个方法(不包含native方法)执行的同时都会创建一个栈帧结构,方法执行过程,对应着虚拟机栈的入栈到出栈的过程。
特点:
-
局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建。而且,局部变量表的大小在编译时期就确定下来了,在创建的时候只需分配事先规定好的大小即可。此外,在方法运行的过程中局部变量表的大小是不会发生改变的。
-
Java虚拟机栈会出现两种异常:*Error和OutOfMemoryError。
a) *Error:
若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出*Error异常。
b) OutOfMemoryError:
若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
- Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
注:*Error和OutOfMemoryError的异同?
*Error表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。
而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。
栈帧(Stack Frame)结构
栈帧是用于支持虚拟机进行方法执行的数据结构,是属性运行时数据区的虚拟机站的栈元素。见上图, 栈帧包括:
-
局部变量表 (locals大小,编译期确定),一组变量存储空间, 容量以slot为最小单位。
-
操作栈(stack大小,编译期确定),操作栈元素的数据类型必须与字节码指令序列严格匹配
-
动态连接, 指向运行时常量池中该栈帧所属方法的引用,为了 动态连接使用。
-
前面的解析过程其实是静态解析;
-
对于运行期转化为直接引用,称为动态解析。
- 方法返回地址
-
正常退出,执行引擎遇到方法返回的字节码,将返回值传递给调用者
-
异常退出,遇到Exception,并且方法未捕捉异常,那么不会有任何返回值。
- 额外附加信息,虚拟机规范没有明确规定,由具体虚拟机实现。
注意:人们常说,Java的内存空间分为“栈”和“堆”,栈中存放局部变量,堆中存放对象。
这句话不完全正确!这里的“堆”可以这么理解,但这里的“栈”只代表了Java虚拟机栈中的局部变量表部分。真正的Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。
异常(Exception)
Java虚拟机规范规定该区域有两种异常:
-
*Error:当线程请求栈深度超出虚拟机栈所允许的深度时抛出但内存空间可能还有很多。
-
OutOfMemoryError:当Java虚拟机动态扩展到无法申请足够内存时抛出内存也全都用光了
2.3 本地方法栈
本地方法栈和Java虚拟机栈实现的功能类似,只不过本地方法区是本地方法运行的内存模型。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
方法执行完毕后相应的栈帧也会出栈并释放内存空间。
异常(Exception):Java虚拟机规范规定该区域可抛出*Error和OutOfMemoryError。
2.4 Java堆
Java堆,是Java虚拟机管理的最大的一块内存,也是GC的主战场,里面存放的是几乎所有的对象实例和数组数据。整个Java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。
- 从内存回收角度,Java堆被分为新生代和老年代;新生代又可被分为:Eden、From Survior、To Survior。
不同的区域存放具有不同生命周期的对象。这样可以根据不同的区域使用不同的垃圾回收算法。这样划分的好处是为了更快的回收内存;
-
从内存分配角度,Java堆可以划分出线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB);这样划分的好处是为了更快的分配内存;
-
堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的,因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError。
对象创建的过程是在堆上分配着实例对象,那么对象实例的具体结构如下:
对于填充数据不是一定存在的,仅仅是为了字节对齐。HotSpot VM的自动内存管理要求对象起始地址必须是8字节的整数倍。对象头本身是8的倍数,当对象的实例数据不是8的倍数,便需要填充数据来保证8字节的对齐。该功能类似于高速缓存行的对齐。
另外,关于在堆上内存分配是并发进行的,虚拟机采用CAS加失败重试保证原子操作,或者是采用每个线程预先分配TLAB内存.
异常(Exception):Java虚拟机规范规定该区域可抛出OutOfMemoryError。
2.5 方法区
方法区主要存放的是已被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。GC在该区域出现的比较少。
特点:
- 线程共享
方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区。
- 永久代
方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为老年代。
- 内存回收效率低
方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效。
对方法区的内存回收的主要目标是:对常量池的回收 和 对类型的卸载。
- Java虚拟机规范对方法区的要求比较宽松。
和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。
异常(Exception):Java虚拟机规范规定该区域可抛出OutOfMemoryError。
2.6 运行时常量池
运行时常量池也是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池除了编译期产生的Class文件的常量池,还可以在运行期间,将新的常量加入常量池,比较常见的是String类的intern()方法。
大小,还允许不实现垃圾回收。
异常(Exception):Java虚拟机规范规定该区域可抛出OutOfMemoryError。
2.6 运行时常量池
运行时常量池也是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池除了编译期产生的Class文件的常量池,还可以在运行期间,将新的常量加入常量池,比较常见的是String类的intern()方法。