1. 运行时数据区
1.1 程序计数器
- 解释器通过改变计数器的值来读取下一个需要执行的字节码指令。
- 线程切换后恢复到正确的执行位置。
1.2 Java 虚拟机栈
每一次函数调用都有一个对应的栈帧压入虚拟机栈,栈帧包含:
- 局部变量表
存放方法参数和局部变量,在非 static 方法中,局部变量表的索引 0 处存放 this。 - 操作数栈
是栈帧中用来计算的一块区域。 - 动态链接
指向运行时常量池中该栈帧所属方法的引用,这个引用在运行期间转化为直接引用,来获取方法的元信息。 - 方法出口
方法执行结束后应该回到的位置地址。
1.3 本地方法栈
本地方法栈是为虚拟机执行 Native 方法,与虚拟机栈作用相似。
1.4 方法区
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,从 jdk1.8 开始,方法区的实现从永久代变成了元空间。
- 运行时常量池
存放编译期生成的各种字面值和符号引用,从 jdk1.7 开始,字符串常量池移到了堆上。
1.5 堆
几乎所有的对象实例和数组都在堆上分配,从 jdk1.7 开始默认开启逃逸分析,若对象未在方法外使用,那么对象可以在栈上分配;为了更好地回收和分配内存,堆又划分为:
- 新生代
Eden 区:大部分情况下对象的出生地。
Survivor 区:GC 回收后 Eden 区幸存的对象会进入这里,年龄 +1。 - 老年代
当对象的年龄达到一定的阈值(对象头中分配了4bit存储分代年龄,因此该值为16),就会进入该区域。
2. 对象的内存布局
- 锁状态标记和 synchronized 锁升级有关
- 开启指针压缩,Class Pointer 占 4 字节
3. 线上内存溢出如何排查?
- 配置参数 -XX:+HeapDumpOnOutOfMemoryError,线上 OOM 时会产生堆内存快照,用可视化工具对快照分析
- ......