Java运行时数据区域
众所周知,Java 虚拟机有自动内存管理机制,如果出现内存泄漏和溢出方面的问题,排查错误就必须要了解虚拟机是怎样使用内存的。
包含:程序计数器(PC)、堆、本地方法栈、虚拟机栈、元空间
下图是 JDK8 之后的 JVM 内存布局。
程序计数器(PC)
- 内存中一块较小的空间,每个线程都有一个 PC,用于存储线程的下一步执行指令。
- 线程执行的命令,也都是从 PC 中获取的。
- 线程间的资源切换,就是获取不同线程的PC中的指令,继续执行的。
- 执行到 Native 方法,计数器值则为空(Undefined)。
本地方法栈
本地方法栈(Native Method Stack
)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native
方法服务。
虚拟机栈
- 一般所聊的栈都是虚拟机栈,每个栈里放着栈帧。
- 一个方法相当于一个栈帧。方法的调用相当于创建一个栈帧,并进行入栈与出栈操作。
- 栈,遵循先进后出原则。栈中栈帧越多,相当于方法套用的层级越多。
- 栈帧相当于栈的数据结构,包括 局部变量表、操作栈、动态链接、方法返回地址等信息。
- 局部变量表,存放方法参数和局部变量的区域。 局部变量没有准备阶段, 必须显式初始化。(类在创建时是有准备阶段的)
- 操作栈,JVM 的执行引擎是基于操作栈的执行引擎。同样是先进后出的栈,用于在方法执行过程中, 会有各种指令往栈中写入和提取信息。
- 动态链接,每个栈帧中包含一个在常量池中对当前方法的引用, 目的是支持方法调用过程的动态连接。
- 方法返回地址
- 正常情况下,返回调用方法的上层地址
- 抛异常情况下,抛给异常处理的栈针
- 异常处理情况下,按照 PC 执行以下个指令(内部方法抛异常,外部方法捕获并处理)
i++ 与 ++i 的区别
int i= 1;
1. i= i++;
解析:先将1放入操作栈,在将 i自增+1,再从操作栈中取出 1,并赋值给 i ,结果 i为1;
2. i= ++i;
解析:先将 i自增+1,再将1放入操作栈,再从操作栈中取出 1,并赋值给 i ,结果 i为2;
栈结构图
堆
- 堆,被所有线程共享,在虚拟机启动时创建。
- 做为 GC 的主要工作区,java 堆也叫作 “GC 堆”。
- 堆采用分代算法。
- 从内存回收的角度来看,青年代和老年代;青年代又分为
Eden空间、From Survivor空间、To Survivor空间
等。从内存分配的角度来看,线程共享的 Java 堆中可能划分出多个线程私有的分配缓冲区。 - Eden 空间、From Survivor 空间、To Survivor 空间内存分配为 8:1:1 。
- 新生代与老年代空间占比 1:2