美团一面,发生OOM了,程序还能继续运行吗?

69 阅读5分钟

大家好,我是明智,接下来的几篇文章我会给大家介绍一系列面试题,这些面试题有些是我面试的时候别人问我的,有些是我自己作为面试官时去问别人的,有些是一些小伙伴在面试后跟我一起讨论过的,经过整理,我会文章的方式慢慢跟大家分享。

今天是这个系列的第一篇:当程序发生OOM后,还能继续运行吗?这个问题看似简单,其实背后有很多值得讨论的点,咱们一起来掰扯掰扯。

什么是OOM?

首先,咱们得搞清楚什么是OOM。OOM,全称Out of Memory,简单来说就是程序在运行过程中,内存不够用了。想象一下,你在电脑上开了十几个网页,又开了个大型游戏,再加上后台一堆程序在跑,突然电脑卡死了,这就是典型的OOM情况。

在程序世界里,当一个应用需要的内存超过了系统能够提供的内存,操作系统就会抛出一个OOM错误,告诉你:“兄弟,内存不够用了!”

OOM的常见原因

造成OOM的原因有很多,常见的有以下几种:

  1. 内存泄漏:程序不断申请内存,确没有正确释放内容,导致内存耗尽。
  2. 大对象:突然需要分配一个很大的对象,比如读取一个超大的文件到内存中,又比如,在进行数据查询时,一次返回十几万甚至上百万条数据。
  3. 并发问题:多个线程同时申请大量内存,瞬间把内存耗尽。

程序发生OOM的后果

那么,当程序发生OOM后,它还能继续运行吗?这个其实跟导致OOM出现的原因有关

如果是内存泄漏的原因导致的OOM,程序往往会崩溃。

这种情况下所有的工作线程都会因为申请内存失败而崩溃,这些工作线程不仅仅是处理用户请求的线程,这些工作线程的任务可能是进行网络IO、可能是你的请求处理线程、还可能是维持心跳的线程。

你会发现这个应用从kafka的消费组中被踢掉了,从服务提供者列表中被踢掉了,当然,如果你对这个应用本身配置了健康检查的话,它最先可能的就是被你的检查检查机制给杀死。写到这里我觉得这个应用也挺难的,做人难,做个应用也真tm难!

如果是大对象导致的OOM,可能可以正常运行。按照笔者的经验来看,很少会有同学一次读个把G的文件到内存里面,一般出现这种问题都是在跑批的时候创建了大量的对象。

假设我们的应用中有一个定时任务,这个定时任务每个月运行一次,在月底的时候将本月所有有过下单的用户查询出来并发送通知。在系统上线初期用户数量少,这样还没有什么问题。但随着用户数量的增加,如果一次性从数据库加载了大量的用户数据到内存中,而没有进行分页或者批处理,那么此时极易出现OOM。

这种情况系统不一定会奔溃,这是因为跑批的线程在出现OOM奔溃后,其申请的内存会释放掉,不会影响其它线程的正常工作。

不过请注意,我说的是不一定,因为

  1. linux本身有系统保护机制叫OOM Killer,当系统内存非常紧张时,它会选择性地杀掉一些进程以释放内存。有可能跑批的线程还没奔溃,程序就被OOM Killer杀死了
  2. 跑批的线程不一定先奔溃,也有可能由于跑批的线程长时间占用了大量的内存,导致其它的工作线程在申请不到足够使用的内存而无法正常工作。

如果是因为并发导致的OOM,持续的超出系统承受能力的大量的并发最终一定会打崩系统。 这一点应该是毋庸置疑的,不过瞬时的高流量并不一定会让系统崩溃,即使出现了OOM,只是会出现部分请求处理失败。

说了这么多,我们接下来聊聊怎么预防OOM?注意,我说的是预防,也就是OOM发生前我们能做什么

如何预防OOM?

及时释放资源,防止内存泄漏

内存泄漏是导致OOM的一个重要原因。我们需要在代码中及时释放不再使用的内存,避免长时间占用内存资源。比如在Java中,使用finally块或try-with-resources语句,确保资源及时释放。

控制大对象的使用

在处理大对象时,要注意分批处理,避免一次性加载到内存中。比如读取大文件时,可以采用流式处理,每次读取一小部分数据进行处理。对于大数据量的查询,应该要分页分批处理。

调整内存配置

针对不同的应用需求,合理配置内存参数。例如,在Java中,可以通过调整JVM的堆内存大小(-Xmx和-Xms参数)来满足应用的内存需求。

并发控制

在高并发场景下,需要合理控制线程的数量,避免过多线程同时申请大量内存。可以使用线程池来管理线程,避免因线程过多导致的内存耗尽。

做好监控

定期监控应用的内存使用情况,及时发现潜在的问题。可以使用一些内存分析工具,如VisualVM、MAT(Memory Analyzer Tool)等,帮助定位和解决内存问题。


作者简介

大三退学,创业、求职、自考,一路升级

7年it从业经验,多个开源社区contributor

专注分享个人成长路上的所悟所得

长期探索 个人成长职业发展副业探索