一、核心区别对比
| 维度 | StackOverFlowError | OutOfMemoryError |
|---|---|---|
| 发生区域 | 虚拟机栈/本地方法栈 | Java堆/方法区/直接内存 |
| 触发原因 | 线程请求的栈深度超过限制 | 内存耗尽无法分配新对象 |
| 是否可恢复 | 不可恢复(线程级) | 有时可恢复(如GC后) |
| 常见场景 | 无限递归、大循环 | 内存泄漏、超大对象 |
| JVM参数控制 | -Xss | -Xms/-Xmx/-XX:MaxMetaspaceSize |
二、发生时机与典型案例
1. StackOverFlowError
发生条件:
-
方法调用层次过深(默认栈深度:Java 1.5+ 约1024-2048层)
-
局部变量表过大(如声明大数组)
典型案例:
// 无限递归
void recursive() {
recursive(); // 每次调用压入一个栈帧
}
JVM栈存储内容:
-
局部变量表:基本类型、对象引用
-
操作数栈:方法执行的工作区
-
动态链接:指向运行时常量池的方法引用
-
方法返回地址:PC寄存器值
2. OutOfMemoryError
发生条件:
-
堆内存不足创建新对象
-
方法区(元空间)加载类过多
-
直接内存(NIO)分配失败
典型案例:
// 内存泄漏
List<Object> leak = new ArrayList<>();
while(true) {
leak.add(new byte[1024*1024]); // 不断消耗堆空间
}
堆存储内容:
-
对象实例:new创建的所有对象
-
数组数据:包括基本类型数组
-
字符串常量池(JDK7后移至堆)
三、JVM内存区域详解
1. 虚拟机栈(JVM Stack)
-
线程私有:每个线程有独立栈
-
栈帧结构:
graph LR A[栈帧] --> B[局部变量表] A --> C[操作数栈] A --> D[动态链接] A --> E[返回地址] -
配置参数:-Xss1M(设置1MB栈大小)
2. Java堆(Heap)
-
线程共享:所有对象实例分配区域
-
分代结构:
Young Generation [Eden + S0/S1] Old Generation -
配置参数:
-
-Xms256m(初始堆大小)
-
-Xmx2g(最大堆大小)
-
四、面试深度问题解析
Q1:如何诊断StackOverFlowError?
排查步骤:
-
获取线程栈信息:
jstack <pid> > thread_dump.log -
分析递归调用链
-
检查局部变量表大小
-
调整栈大小测试(-Xss参数)
Q2:OOM后如何让程序继续运行?
恢复方案:
try {
byte[] data = new byte[1024*1024*1024];
} catch (OutOfMemoryError e) {
System.gc(); // 尝试触发GC
// 降级处理(如改用磁盘缓存)
}
注意事项:
-
只能处理特定场景(如临时大对象分配)
-
不能解决内存泄漏问题
Q3:元空间OOM与永久代OOM区别?
演进对比:
| 特性 | 永久代(≤JDK7) | 元空间(≥JDK8) |
|---|---|---|
| 存储内容 | 类元数据、字符串常量池 | 仅类元数据 |
| 内存位置 | JVM堆内 | 本地内存 |
| 配置参数 | -XX:PermSize | -XX:MaxMetaspaceSize |
| 触发OOM条件 | 类加载过多 | 元数据超限/OS内存不足 |
五、预防与调优建议
1. StackOverFlow预防
-
限制递归深度(改用循环)
-
避免栈帧过大(拆分方法)
-
适当增加栈大小(-Xss)
2. OOM预防策略
-
合理设置堆大小(-Xmx)
-
使用内存分析工具(MAT/JProfiler)
-
避免内存泄漏(及时释放引用)
-
监控关键指标(GC频率/老年代占比)
理解这些差异有助于:
-
快速定位内存相关问题
-
合理设计JVM参数
-
编写更健壮的代码

2848

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



