JVM实战(31)——内存溢出之请求超时

一、简介

本章,我们将通过实际案例讲解一个Web应用的内存溢出问题,该内存溢出问题的排查涉及Tomcat的一些底层原理,最终排查发现是由于请求超时问题导致,我们先来看下系统的背景。

1.1 系统背景

生产环境的一个系统发生告警,拿到生产日志后出现如下字样:
Exception in thread "http-nio-8080-exec-1089" java.lang.OutOfMemoryError:Java heap space

很明显,Java堆内存区域发生了内存溢出异常。特别要注意的是http-nio-8080-exec-1089,由于当时系统部署在tomcat中(8080端口),所以上面这段日志的意思就是tomacat工作线程在处理请求时发生了内存溢出异常。

为什么会是tomcat的工作线程发生异常? 这就涉及tomcat的一些基本原理。

1.2 tomcat基本原理

首先,我们明确一点 Tomcat运行时本身就是一个JVM进程 ,我们写好的程序打包后放到tomcat的指定目录下,程序中的各种类会由Tomcat加载到它的JVM内存区域中,然后由tomcat来执行我们程序中的类:

tomcat有许多自己的工作线程,它们默认会监听8080端口。8080端口上收到的请求会均匀分配给这些工作线程,工作线程接收到请求后负责调用程序自身的Servlet进行处理。上述异常日志中的http-nio-8080-exec-1089,说白了就是上图中的tomcat工作线程,因为它负责调用Spring中的一大堆代码,发现运行时堆内存不够了,所以就抛出了异常。

Spring Boot应用可以把web容器直接内嵌在我们打包后的程序中,但本质还是一样的。

二、问题分析

知道了系统的大致情况,我们就要用MAT来分析下事故现场的堆内存快照了(线上系统记得加上JVM参数-XX:+HeapDumpOnOutOfMemoryError)。

2.1 内存快照

我们分析内存快照,首先要找到占用堆内存最大的对象。我们发现有一大堆byte[]数组占据了大约8G的内存,而当时线上机器给Tomcat的JVM堆内存也是8G。这说明,tomcat工作线程在处理请求时大量创建了这些byte[]数组,直接把堆内存占满了,从而导致内存溢出。

然后,我们继续分析这些byte[]数组到底是个啥,通过MAT找了很多类似下面这样的数组,每个10MB,一共约800个,总量约8G:

通过MAT的引用分析,发现这些数组都被一个名为org.apache.tomcat.util.threads.TaskThread的Tomcat类引用着,这个一看就是Tomcat自己的线程类。MAT可以查看当前JVM中有哪些线程存在,我们发现上述种tomcat线程一个约400个,每一个引用着2个byte[]数组。

也就是说: 400个tomcat工作线程同时在处理请求,每个线程创建了2个10MB的byte[]数组,结果就总共创建了8G的数组,进而导致了内存溢出 。

2.2 请求超时

根据上述分析,我们的脑海里应该有这样一副流动画面:1秒钟内来了400个请求,导致tomcat的400个工作线程同时开始处理请求,每个线程在处理请求时会创建2个10MB的byte[]数组对象,用于自用,结果瞬间把8G内存空间占满,触发内存溢出异常

但是,我们通过监控系统发现,事故现场的QPS只有100,而不是400!出现这种情况只有一种可能,请求超时了,每个请求的处理时间达到4s,这样4s内400个工作线程会在同时工作,进而导致上述问题。

那么现在就剩下两个问题:

  1. 每个tomcat工作线程创建的2个10MB的byte数组究竟是啥?
  2. 程序哪里出现了大量超时?

先来看第一个,tomcat的配置文件中有一个max-http-header-size:10000000配置,根据查阅tomcat文档我们知道,这个是tomcat工作线程为请求和响应创建的数组,可以适当调小些,但是10MB也在合理范围内。所以,问题的根本原因就是程序超时。

我们通过程序日志发现,有大量的Timeout Exception字样,这是程序在通过RPC调用其他系统接口时抛出的,然后通过RPC超时参数配置发现,超时时间刚好是4s!

也就是说,在某一段时间内,某个外部依赖系统刚好挂掉了,导致我们系统通过RPC调用它的接口时出现大量超时,而在超时的4s内,工作线程会hang住,从而引发内存溢出,所以 这个4s请求超时的配置是根本原因 。

三、系统优化

分析清楚了问题原因,优化就很简单了,直接将超时时间改为1s就可以了。这样的话,每秒100个请求过来,每个拥有2个byte数组,那总共就是2G,不会将JVM堆内存占满,然后超过1s就超时,请求结束。

超时时间的配置要根据系统运行时模型合理配置。除此之外,一些核心系统,必须要有熔断、降级、限流的机制,可以通过Hystrix来实现,没有接触过的读者可以参阅Hystrix官方资料了解。

四、总结

本章,我们通过一个实际案例分析了因为请求超时引起的内存溢出问题,以及相应的排查思路。解决这类问题的思路其实都是一致的,需要一步步去分析,在实践中积累经验,举一反三。

相关推荐

  1. JVM实战33)——内存溢出内存使用率过高

    2024-01-25 18:02:01       59 阅读
  2. JVM实战(27)——内存溢出概述

    2024-01-25 18:02:01       62 阅读
  3. JVM实战(29)——模拟栈内存溢出

    2024-01-25 18:02:01       55 阅读
  4. JVM内存溢出排查

    2024-01-25 18:02:01       43 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-25 18:02:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-25 18:02:01       101 阅读
  3. 在Django里面运行非项目文件

    2024-01-25 18:02:01       82 阅读
  4. Python语言-面向对象

    2024-01-25 18:02:01       91 阅读

热门阅读

  1. 网络安全产品之认识漏洞扫描设备

    2024-01-25 18:02:01       69 阅读
  2. LeetCode 2765. 最长交替子数组

    2024-01-25 18:02:01       67 阅读
  3. ChatGPT在车联网的应用创新浅谈

    2024-01-25 18:02:01       62 阅读
  4. GPT帮我快速解决工作上的问题案例

    2024-01-25 18:02:01       62 阅读
  5. windows环境下设置redis在后台运行

    2024-01-25 18:02:01       63 阅读
  6. LeetCode69. Sqrt(x)

    2024-01-25 18:02:01       52 阅读
  7. 代码随想录算法训练营29期Day29|LeetCode 491,46,47

    2024-01-25 18:02:01       61 阅读
  8. 数据结构刷题笔记

    2024-01-25 18:02:01       57 阅读
  9. 杂七杂八的命令

    2024-01-25 18:02:01       56 阅读
  10. 【Ubuntu】systemctl 命令

    2024-01-25 18:02:01       59 阅读
  11. Vue3全局组件和自定义指令

    2024-01-25 18:02:01       64 阅读