Java捕获OutOfMemoryError后程序仍可继续运行的原因咨询
为什么测试中OOM被捕获后仍能正常运行
你遇到的是非常特殊的温和OOM场景,核心原因是:
你触发OOM的操作是尝试分配new Integer[10000000 * 10000000]这个超大数组,这个数组的总大小远超过你设置的1MB堆上限,JVM在分配前检查到空间不足直接抛出OutOfMemoryError,这个数组自始至终都没有分配成功,也没有占用任何堆内存。
所以异常被catch之后,堆可用空间和执行test方法前几乎没有差别,后续你只需要分配100个int的小数组,完全在可用空间范围内,自然可以正常执行。加上ScheduledExecutorService按固定频率调度任务,每次执行前之前的临时对象都已经被GC回收,所以会反复输出相同的日志。
这个测试结果不具备普遍性
不要觉得所有OOM被捕获后都能正常运行,生产环境的绝大多数OOM场景捕获后都无法正常执行业务:
- 如果是内存泄漏累积导致的OOM:整个堆几乎被存活对象占满,随便分配小对象都会触发OOM,就算捕获了异常,后续只要有对象分配操作还是会反复抛异常,根本跑不动。
- 如果OOM发生在JVM核心流程:比如GC、类加载、线程创建阶段抛出OOM,就算捕获了,JVM内部状态已经被破坏,后续大概率会出现逻辑错乱、未知崩溃的问题。
关于是否应该捕获Throwable的说明
工业界普遍不建议随意捕获Throwable:Error类型的异常本身就是用来标识「程序正常运行时不应该出现的严重问题」,大部分Error出现后程序状态已经不可控,强行继续运行很容易出现数据损坏、逻辑不一致的问题。
只有少数特定场景可以做有限捕获:比如需要上报错误日志、避免单个任务异常拖垮整个服务时,可以在任务最外层入口捕获Throwable,完整记录错误栈、内存快照后,要么主动优雅退出进程,要么丢弃异常任务做降级,绝对不能吞掉异常后当做什么都没发生继续执行业务。
你测试用的代码和运行命令供参考:
public class TestErrorInThread { public static void test() { System.out.println("Running the test at time " + new Date()); try { System.out.println("Inside try block"); Integer[] array = new Integer[10000000 * 10000000]; } catch (Throwable e) { System.out.println("Inside catch block"); System.out.println(e); } int arr[] = new int[100]; System.out.println("Programme is still running..."); } public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); Runnable runnable = TestErrorInThread::test; scheduledExecutorService.scheduleAtFixedRate(runnable, 0, 5, TimeUnit.SECONDS); } }
运行命令:
java -Xmx1m TestErrorInThread
内容的提问来源于stack exchange,提问作者AkhileshSoni
相关产品推荐
相关产品推荐

