文章目录
JVM运行时内存区域
- JVM 运行时内存区域分为五个:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。
- 线程私有的:程序计数器、虚拟机栈、本地方法栈。
- 线程共享的:堆、方法区。

一、程序计数器(Program Counter)
- 程序计数器存放下一条字节码指令地址,被称作指令指针(instruction pointer,IP),是一块较小的内存空间。
- 程序计数器是当前线程所执行字节码的行号指示器。
- 字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 分支、循环、跳转、异常处理、线程恢复等功能都需要依赖计数器来完成。
- 程序计数器是线程私有的,每条线程都有一个独立的程序计数器:线程切换后恢复到正确的执行指令,各个线程之间计数器互不影响。
- 程序计数器是JVM内存中唯一没有规定OutMemoryError的区域。
二、Java虚拟机栈 (Java Virtual Machine Stacks)
- 虚拟机栈描述Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- Java虚拟机栈线程私有,生命周期与线程相同:线程创建时,相应的区域分配内存,线程销毁时,释放相应内存。
- 每个线程拥有一个「虚拟机栈」,每个「虚拟机栈」拥有多个「栈帧」,而栈帧则对应着一个方法。方法运行结束则意味着该「栈帧」出栈。
- 每个「栈帧」包含局部变量表、操作数栈、动态链接、方法返回地址。
- 当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
- 虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

- 局部变量表:存放了编译器可以知道的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)。
- 局部变量表所需要的空间是在编译期间完成分配的,当进入一个方法时这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法的运行期间不会改变局部变量的大小。
三、本地方法栈(Native Method Stack)
- 本地方法栈为虚拟机使用到的操作系统原生本地方法服务。
- 某些虚拟机本地方法栈与虚拟机栈合二为一。
- 本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
- 线程私有。
- 内存不足时会抛出StackOverflowError异常和OutOfMemoryError异常。
四、Java堆(Heap)
- 创建的对象实例几乎都是存放在堆内存中。
- 堆是虚拟机所管理的最大的一块内存区域。
- 堆是被所有线程共享的一块内存区域。
- (如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。)
- 堆可以处于物理上不连续的内存空间中,内存不足时会抛出OutOfMemoryError异常。
- 垃圾回收是针对堆上不同区域的对象进行扫描回收。
- 堆内存不足时会抛出OutOfMemoryError异常:当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
- 堆内存区域分为新生代(Young Generation)、老年代(Old Generation)。
- 新生代分为Eden空间、From Survivor空间、To Survivor。
- 无论哪个区域,存放的都是对象实例。进一步划分的目的是为了更好的回收内存或者更快的分配内存。
对象首先在 Eden 区域分配.
在一次新生代垃圾回收后,如果对象还存活,则会进入 S0 或者 S1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1).
当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中.
对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置.

五、方法区(Method Area)
- 方法区存储每个类的元数据信息:已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 方法区是所有线程共享的一块区域。
- 不同的虚拟机对于方法区的实现不同。
- 和 Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾回收。垃圾回收在这个区域是比较少出现的。
- 方法区内存不足时会抛出OutOfMemoryError异常。
六、问答
方法区和永久代以及元空间?
1、永久代、元空间是方法区的两种实现方式。
2、永久代是 JDK 1.8 之前的方法区实现,JDK 1.8 及以后方法区的实现变成了元空间。
为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?
1、永久代有一个 JVM 本身设置的固定大小上限,无法进行调整
2、元空间使用的是本地内存
3、元空间参数:
-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
参考:
https://zhangpan.site/2020/09/04/26.JVM%20memory/
https://mp.weixin.qq.com/s?__biz=MzkzMDI1NjcyOQ==&mid=2247487735&idx=1&sn=96bb492a25ad7ff1875cdf649c9ab611&source=41#wechat_redirect
1695

被折叠的 条评论
为什么被折叠?



