目录
Java内存区域与内存溢出异常
运行时数据区域
- 程序计数器
可以看做是,当前线程所执行的字节码的行号指示器。每个线程需要一个独立的程序计数器。
- Java虚拟机栈
1.生命周期与线程相同,每个方法执行的时候都会同步创建一个栈帧,用于存储局部变量表、方法出口等信息。
2.局部变量表的槽数量是在编译期间完成完成,当进入某个方法时,栈帧需要多大的局部变量空间是确定的,在运行期间不会改变表大小。
3.线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。
*.与通常C++所说堆栈中的栈不同,其更多情况下指的只是虚拟机栈中的局部变量表。
*.虚拟机的栈容量是不可以动态扩展的。但是线程在进入方法时申请栈失败时仍然会出现OOM。
- 本地方法栈
1.Java虚拟机栈为字节码服务,而本地方法栈为被native修饰的本地方法服务。
2.栈深度溢出也会抛StackOverflowError异常。
3.栈扩展失败时会跑OOM。
*.HotSpot虚拟机把虚拟机栈和本地方法栈合二为一。
- 堆
是垃圾收集器管理的区域,也被称为GC堆。几乎所有对象存放的位置。
- 方法区
1.也称作非堆。与堆一样,是各个线程共享的内存区域。
2.存储被虚拟机加载的常量、静态变量、类型信息、代码缓存等数据。
分为:
1)运行时常量池:用于存放字面量和符号引用。String.intern()可在运行期将新常量放入常量池。
String.intern()如果存在则返回常量池那个对象的引用,否则写入常量池,并返回当前对象的引用。
2)元空间
3.方法区的回收目标是,对常量池的回收和堆类型的卸载。
*.jdk1.8完全废弃永久代,把字符串常量池,静态变量移到运行时常量池,把类型信息等移到元空间中。
对象的创建
当Java虚拟机遇到一条字节码new
时:
- 在常量池定位类的符号引用
- 检查类是否被加载 解析 初始化过
- 执行相应类加载过程(第七章)
- 为新生对象分配内存,根据内存是否规整,采用不同的内存分配方式:
- 带有空间压缩整理的垃圾收集器,例如,Serial、ParNew等收集器:采用简单高效的
指针碰撞
分配。 - 采用CMS基于清除(Sweep)算法收集器:采用较为复杂的空间
列表分配
。
- 带有空间压缩整理的垃圾收集器,例如,Serial、ParNew等收集器:采用简单高效的
- 将分配到的内存初始化为零
- 对对象进行必要的设置,例如,对象是哪个类的实例、类的元数据信息、对象的GC分代年龄。这些存放在对象头中
- 执行构造函数的
<init>()
方法 - 可用的对象才算完全被构造出来
并发分配内存的问题
两种解决方法:
- CAS配上失败重试方式保证原子性(默认使用)
- 每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲TLAB,如果本地缓冲用完了就需要使用1方法。
可以通过参数设置,-XX:+UseTLAB
,-XX:-UseTLAB
对象的内存布局
对象在堆中的布局划分为三部分:
-
对象头
- 存储对象自身的运行时数据,哈希吗、GC分代年龄、锁状态标志等,长度对应32位和64位虚拟机。
- 类型指针,虚拟机通过这个指针确定该对象是哪个类的实例。
-
实例数据
父类继承的或者自身的都必须记录起来。相同宽度类型的字段会被分配到一起。
存储顺序收到虚拟机配置策略参数和源码顺序影响。
父类定义的变量会排列到子类之前,通过-XX:CompactFields,默认也为true,将子类较窄的变量插入父类变量的间隙中,以节省一点点的空间。
-
对齐填充
起占位符的作用。任何对象的大小都必须是8字节的整数倍。
对象的访问定位
Java程序通过栈上引用的数据来操作堆上的对象,访问方式主要有:
-
句柄
需要句柄池间接访问实例。
-
直接指针
直接访问实例,HotSpot使用。
Java堆溢出,OOM测试
目录下多了java_pid3468.hprof
堆转储快照
import java.util.ArrayList;
import java.util.List;
/**
* VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* @author liuguanliang
* @since 2020-5-24 22:45:45
*/
public class HeapOOM {
static class OOMObject {
}
public static void main (String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}
虚拟机栈溢出
-
线程请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverflowError
-
栈内存动态扩展,当无法申请足够时抛出OOM
HotSpot虚拟机选择不支持栈动态扩展
减少栈内存容量:
VM Args: -Xss128k
方法区溢出
JDK 8以后,永久代完全退出历史舞台,元空间作为其替代者出场。很难再迫使虚拟机产生方法区的溢出异常了。
参考:《深入理解Java虚拟机》 第3版 周志明