当你被问到“当Java程序发生内存溢出时,进程还能正常处理请求吗?”这样的面试题,会不会很懵?这里分享一次网友车辙在当初刚毕业那几年,意义风发,总觉得天下没有自己不会的面试题。然后在一次字节的面试中,彻彻底底的翻车的面试过程,希望提供大家一些面试经验。

Java 的优势有什么

面试官一上来,直接进入主题:你觉得在内存管理上,Java 有什么优势?

我:小菜一碟。相对于需要手动释放内存的 C 语言,Java 则通过垃圾回收机制实现了自动内存管理。这一机制能够自动辨识并清理不再使用的内存资源,从而省去了繁琐的手动释放过程,极大地简化了开发人员的工作。这让开发者能够将精力更专注地放在业务逻辑的构建上,而不必过多忧虑内存管理的问题,从而大大简化了开发人员的工作量。

什么是 OOM

面试官:请问您是否熟悉内存溢出(OOM)情况?

我:在我的经验中,我在线上经常遇到内存溢出的情况。特别是在使用Java编写的应用程序中,OOM通常是指内存溢出异常,即当应用程序需要为新对象分配内存空间时,可用内存不足以满足需求,从而导致了OOM异常的抛出。

什么情况会产生 OOM

面试官:好小子,线上的事故代码不会都是你写的吧,那你能谈谈是什么情况会导致内存溢出呢?

我:当然,一个常见的情况就是堆内存溢出。在创建对象时,大部分情况下都是占用 JVM 的堆内存。一旦堆内存无法满足对象的分配需求,就会抛出OOM异常。

错误信息通常是:java.lang.OutOfMemoryError: Java heap space

堆内存溢出的具体场景

面试官:你这个太抽象了,能不能具体点?

我:嗯,常见导致内存溢出的情况有这么几种:

对象生命周期过长:如果某个对象的生命周期过长,而且该对象占用的内存很大,那么在不断创建新对象的过程中,堆内存会被耗尽,从而导致内存溢出。这种情况一般出现在用集合当缓存,却忽略了缓存的淘汰机制。

无限递归:递归调用中缺少退出条件或递归深度过大,会导致空间耗尽,引发溢出错误。往往在测试环境就会发现该问题,不会暴露在生产环境

大数据集合:在处理大量数据时,如果没有正确管理内存,例如加载过大的文件、查询结果集过大等,会导致内存溢出。

JVM配置不当:如果JVM的内存参数配置不合理,例如堆内存设置过小,无法满足应用程序的内存需求,也会导致内存溢出。

下面的这个例子就是无限循环导致内存溢出。

List list = new ArrayList();
while (true) {
list.add(1);
}

什么是内存泄漏

面试官:你知道在我们的程序里,有可能会出现内存泄漏,你对它了解吗?

我:对的,和内存溢出的情况不同,还有一种特殊场景,叫做内存泄漏(本质上还是内存溢出,只不过是错误的内存溢出),指的是程序在运行过程中无法释放不再使用的内存,导致内存占用不断增加,最终耗尽系统资源,这种情况就被称为内存泄漏。

这一次,我提前抢答了, 常见导致内存泄漏的情况包括:

对象的引用未被正确释放:如果在使用完一个对象后,忘记将其引用置为 null 或者从数据结构中移除,那么该对象将无法被垃圾回收,导致内存泄漏。比如 ThreadLocal。

长生命周期的对象持有短生命周期对象的引用:如果一个长生命周期的对象持有了一个短生命周期对象的引用,即使短生命周期对象不再使用,由于长生命周期对象的引用仍然存在,短生命周期对象也无法被垃圾回收,从而造成内存泄漏。

过度使用第三方库:某些第三方库可能存在内存泄漏或者资源未正确释放的问题,如果使用不当或者没有适当地管理这些库,可能会导致内存溢出。

集合类使用不当:在使用集合类时,如果没有正确地清理元素,当集合不再需要时,集合中的对象也不会被释放,导致内存泄漏。

资源未正确释放:如果程序使用了诸如文件、数据库连接、网络连接等资源,在不再需要这些资源时没有正确释放,会导致资源泄漏,最终导致内存泄漏。

下面的这个例子就是长生命周期的对象持有短生命周期对象的引用, 导致内存泄漏。

List list2 = new ArrayList();

@GetMapping("/headOOM2")
public String headOOM2() throws InterruptedException {
while (true) {
list2.add(1);
}
}

还有其他情况吗

面试官:你说的都是堆的内存溢出,还有其他情况吗?

递归调用导致栈溢出

当递归调用的层级过深,栈空间无法容纳更多的方法调用信息时,会引发 StackOverflowError 异常,这也是一种 OOM 异常。例如,以下示例中的无限递归调用会导致栈溢出。

public class RecursiveExample {
public static void recursiveFunction() {
recursiveFunction();
} public static void main(String[] args) {
recursiveFunction();
}
}

元空间(Metaspace)耗尽

元空间是 Java 8 及以后版本中用来存储类元数据的区域。它取代了早期版本中的永久代(PermGen)。元空间主要用于存储类的结构信息、方法信息、静态变量以及编译后的代码等。

当程序加载和定义大量类、动态生成类、使用反射频繁操作类等情况下,可能会导致元空间耗尽。常见导致元空间耗尽的情况包括:

类加载过多:如果应用程序动态加载大量的类或者使用动态生成类的方式,会导致元空间的使用量增加。如果无法及时卸载这些类,元空间可能会耗尽。

字符串常量过多:Java中的字符串常量会被存储在元空间中。如果应用程序中使用了大量的字符串常量,尤其是较长的字符串,可能会导致元空间的耗尽。

频繁使用反射:反射操作需要大量的元数据信息,会占用较多的元空间。如果应用程序频繁使用反射进行类的操作,可能会导致元空间耗尽。

大量动态代理:动态代理是一种使用反射创建代理对象的技术。如果应用程序大量使用动态代理,将会生成大量的代理类,占用较多的元空间。

未正确限制元空间大小:默认情况下,元空间的大小是不受限制的,它会根据需要动态扩展。如果没有正确设置元空间的大小限制,或者限制过小,可能会导致元空间耗尽。

下面的这个例子就是类加载过多导致的内存泄漏。

public class OOMExample {
public static void main(String[] args) {
while (true) {
ClassLoader classLoader = new CustomClassLoader();
classLoader.loadClass("com.example.LargeClass");
}
}
}

终极问题

面试官满意地点了点头,表示我对Java线程处理请求时的情况了解得很多。接着,他提出了一个问题:“当Java线程在处理请求时,发生了OOM异常,整个进程还能继续处理请求吗?”这个Java面试题可不简单哦!

在我正准备回答的时候,面试官却提醒我这个问题涉及的内容较为复杂,不是简单的是与否问题。他建议我先整理一下思路。

面试官的眼神透露出这道题目有些深意。经过一番思考,我给出了我的答案:“我认为OOM并不会导致整个进程崩溃。”

面试官随即追问:“你是怎么理解的?OOM难道不是内存不足的表现吗?既然内存不足,进程还能继续处理请求吗?”

我解释道:“尽管出现OOM,但通过垃圾回收机制仍有可能释放一些内存。”

面试官却反驳:“不是因为在垃圾回收后,发现内存依然不足才会抛出OOM异常吗?这难道不意味着垃圾回收已经无法继续执行,导致内存不足,进而触发了OOM异常吗?整个流程是内存不足,垃圾回收无效,最终OOM。”

我只能在内心默默吐槽,面对这样的组合拳,我有些措手不及。很遗憾,面试最终以失败告终。面试官在我离开时似乎在暗示:“这种情况我也不愿意发生,只能怪编程经验不够丰富。”

实战

回到家,我马上去进行了代码实战,用来测试 OOM。

环境是:OpenJdk 11 -Xms100m -Xmx100m -XX:+PrintGCDetails

堆内存溢出

首先我们创建一个方法,调用它,每隔一秒不停的循环打印控制台信息,它的主要作用是模拟其他线程处理请求。

@GetMapping("/writeInfo")
public String writeInfo() throws InterruptedException {
while (true) {
Thread.sleep(1000);
System.out.println("正在输出信息");
}
}

接着再创建一个死循环往 List 中放入对象的方法,它的主要作用是模拟导致OOM的那个线程。

@GetMapping("/headOOM")
public String headOOM() throws InterruptedException {
List list = new ArrayList();
while (true) {
list.add(1);
}
}

最终结果是headOOM抛出了 OOM 异常,但是控制台还在不停的打印。【这边截图太大了,就不贴出来了】

这就是答案吗?其实不是,在第一步中,仅仅是在控制台打印出了日志,并没有创建明确的对象。将它稍微改动下,加一行,每次打印前先创建 10M 的对象。

public String writeInfo() throws InterruptedException {
while (true) {
Thread.sleep(1000);
Byte[] bytes = new Byte[1024 * 1024 * 10];
System.out.println("正在输出信息");
}
}

结果依旧会继续打印。看到这里有些人可能会说,答案确实是”还能继续执行”,我只能说你是 Too Young Too Simple 。往下看

堆内存泄漏

老规矩,还是上面的方法

public String writeInfo() throws InterruptedException {
while (true) {
Thread.sleep(1000);
Byte[] bytes = new Byte[1024 * 1024 * 10];
System.out.println("正在输出信息");
}
}

创建一个内存泄漏的方法,list2 作用域是在类对象级别,从而产生内存泄漏

List list2 = new ArrayList();
@GetMapping("/headOOM2")
public String headOOM2() throws InterruptedException {
while (true) {
list2.add(1);
}
}

然后继续执行,结果首先是headOOM2这个方法对应的线程抛出 OOM。

接着是 WriteInfo这个方法对应的线程抛出OOM,所以我猜测现在整个进程基本都不能处理请求了。

为了印证这个猜测,再去调用下 writeInfo这个方法,直接抛出 OOM 异常。说明我们的猜测是对的。

这时候你如果把那个 10M 改成1M,writeInfo 这个方法就又能执行下去了,不信的话就去试试看吧。

这说明内存泄漏的情况,其他线程能否继续执行下去,取决于这些线程的执行逻辑是否会占用大量内存。

不发生内存泄漏的情况下,为什么频繁创建对象会导致OOM,GC 不是会把对象给回收吗

  1. 堆内存限制:Java程序的堆内存有大小限制。如果频繁创建对象且无法及时回收,堆空间可能被耗尽。垃圾回收器会尝试回收不再使用的对象,但若创建速度超过回收速度,堆内存不足将导致OOM。
  2. 垃圾回收开销:尽管垃圾回收器回收不再使用的对象,但垃圾回收本身消耗时间和计算资源。频繁创建临时对象会增加回收时间,降低应用程序效率。
  3. 内存碎片化:频繁创建和销毁对象会导致内存空间碎片化。即使总的空闲内存足够,若没有足够的连续大块内存分配给新对象,也会发生OOM。

通过观察GC日志,可能发现OOM发生时,堆大小未达到阈值,进一步说明其他因素导致了OOM异常。

总结

首先,我们为你阐述了何为OOM以及其发生场景,包括内存溢出和内存泄漏,然后引出了问题:当Java线程在处理请求时,抛出OOM异常,整个进程是否仍能处理请求

随后,我们进行了代码实验,模拟了内存溢出和内存泄漏两种情况,得出以下结论:

  1. 内存溢出情况下,若GC速度跟不上内存分配速度,导致OOM并杀死线程,通常整个进程仍能继续处理请求。
  2. 内存泄漏情况下,未能回收的内存可能引发OOM,继而杀死线程以防止无法回收对象继续产生。此时,那些不占用大量内存的线程可能会继续执行,但那些耗费大量内存的线程可能会无法执行。极端情况下,进程可能会崩溃。

以上结论反映了OOM对进程的影响,并指出了在不同情况下,进程是否仍能处理请求。

Java内存溢出时,还能正常处理请求吗?的更多相关文章

  1. java内存溢出和内存泄露

    虽然jvm可以通过GC自动回收无用的内存,但是代码不好的话仍然存在内存溢出的风险. 最近在网上搜集了一些资料,现整理如下: —————————————————————————————————————— ...

  2. Java内存溢出的详细解决方案

    本文介绍了Java内存溢出的详细解决方案.本文总结内存溢出主要有两种情况,而JVM经常调用垃圾回收器解决内存堆不足的问题,但是有时仍会有内存不足的错误.作者分析了JVM内存区域组成及JVM设置虚拟内存 ...

  3. 老李案例分享:定位JAVA内存溢出

    老李案例分享:定位JAVA内存溢出   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.在poptest的loadrunner的培 ...

  4. Java内存溢出分析方法(Eclipse Memory Analyzer 使用简单入门)

    转载至:http://outofmemory.cn/java/jvm/OutOfMemoryError-analysis 工具 安装Memory Analyse Tools(MAT) 工具, 可以直接 ...

  5. java内存溢出问题

    相信有一定java开发经验的人或多或少都会遇到OutOfMemoryError的问题,这个问题曾困扰了我很长时间,随着解决各类问题经验的积累以及对问题根源的探索,终于有了一个比较深入的认识. 在解决j ...

  6. Java内存溢出和内存泄露后怎么解决

    1.首先这里先说一下内存溢出和内存泄露的区别: 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但 ...

  7. Java内存溢出优化性能优化

    高性能应用构成了现代网络的支柱.LinkedIn有许多内部高吞吐量服务来满足每秒数千次的用户请求.要优化用户体验,低延迟地响应这些请求非常重要. 比如说,用户经常用到的一个功能是了解动态信息——不断更 ...

  8. java内存溢出的解决思路

    原文地址:https://www.cnblogs.com/200911/p/3965108.html 内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能 ...

  9. [转]Java内存溢出详解及解决方案

    原文地址:http://blog.csdn.net/xianmiao2009/article/details/49254391 内存溢出与数据库锁表的问题,可以说是开发人员的噩梦,一般的程序异常,总是 ...

  10. Java基础学习总结(30)——Java 内存溢出问题总结

    Java中OutOfMemoryError(内存溢出)的三种情况及解决办法 相信有一定java开发经验的人或多或少都会遇到OutOfMemoryError的问题,这个问题曾困扰了我很长时间,随着解决各 ...

随机推荐

  1. 2020-12-30:生产环境 CPU 占用过高,你如何解决?

    福哥答案2020-12-30: 1.top + H 指令找出占用 CPU 最高的进程的 pid. 2.top -H -p.在该进程中找到,哪些线程占用的 CPU 最高的线程,记录下 tid. 3.js ...

  2. 2022-02-08:k8s安装centos,yaml如何写? 注意:如果不配置参数,centos容器会处于terminated状态。如何让容器处于running状态?

    2022-02-08:k8s安装centos,yaml如何写? 注意:如果不配置参数,centos容器会处于terminated状态.如何让容器处于running状态? 答案2022-02-08: 加 ...

  3. 从GFS到GPT,AI Infra的激荡20年

    ​导读 最近AIGC和LLM的浪潮层层迭起,大有把AI行业过去十年画的饼,一夜之间完全变现的势头.而 AI Infra (构建AI所需的基础设施),也成了讨论的焦点之一.大众对AI Infra的关注点 ...

  4. ICANN 2001-Learning to Learn Using Gradient Descent

    Key Gradient Descent+LSTM元学习器 解决的主要问题 在之前的机器学习的学习方法中,不会利用到之前的经验,利用到之前经验的"knowledge transfer&quo ...

  5. 【数据库】时区及JDBC的时区设置

    JDBC连接时有个TimeZone配置,这玩意到底有用吗?我是使用Postgresql和Mysql两个数据库验证的.结果如下: 数据库 部署方式 版本 JDBC连接TimeZone参数 JDBC连接s ...

  6. C盘清理,移动node 依赖和缓存文件

    由于先前安装的node 没有做任何配置,都是傻瓜式下一步,导致了我很多依赖都放置C盘,内存占用过多:也不太好管理所有觉得将它移动到node安装目录 一.新建文件夹 在原本安装的nodejs目录下新建 ...

  7. [ 基于宝塔部署 ] 恋爱博客 -- Like_Girl 5.0

    1)环境准备 云服务器 [ CentOS 7 ] 域名解析 love.daxiaoba.cool 宝塔面板 yum install -y wget && wget -O install ...

  8. 在.net项目中添加Husky.Net提交验证

    参考:C# 项目添加 husky - jesn - 博客园 (cnblogs.com) 官方文档:Getting Started | Husky.Net (alirezanet.github.io) ...

  9. 13. 注解配置SpringMVC

    使用配置类和注解代替web.xml和SpringMVC配置文件的功能 13.1.创建初始化类,代替web.xml 在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.Se ...

  10. 让AI支持游戏AI模型:从经典AI算法到最新技术的应用

    目录 20. 让 AI 支持游戏AI模型:从经典 AI 算法到最新技术的应用 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.2.2 最新技术介绍 3. 实现步骤与流 ...