【问题标题】:How to profile spring-boot application memory consumption?如何分析 spring-boot 应用程序内存消耗?
【发布时间】:2021-06-05 14:08:02
【问题描述】:

我有一个 spring-boot 应用程序,我怀疑它可能存在内存泄漏。随着时间的推移,内存消耗似乎增加了,在我重新启动应用程序之前占用了大约 500M 的内存。重新启动后,它需要大约 150M。 spring-boot 应用程序应该是一个非常无状态的 rest 应用程序,并且在请求完成后不应该留下任何对象。我希望垃圾收集器能解决这个问题。

目前在生产中,spring-boot 应用程序似乎使用了 343M 的内存 (RSS)。我得到了应用程序的堆转储并对其进行了分析。根据分析heapdump只有31M大小。那么缺失的 300M 在哪里呢? heapdump 与应用程序使用的实际内存有何关联?我如何分析堆转储后的内存消耗?如果使用的内存不在堆中,那么它在哪里?如何发现spring-boot应用程序的内存消耗是什么?

【问题讨论】:

  • Eclipse MAT 可以做到这一点。还有更多:dzone.com/articles/…
  • 我认为它只分析堆。而且堆似乎并没有占用那么多内存。所以我想知道我是否会从堆分析中受益
  • 也许您应该告诉我们您如何以及在哪里/在哪个工具中看到内存消耗。也许您正在尝试解决一个不存在的问题
  • 我用visualvm分析了heapdump文件。还有基本的 linux 工具来查看内存消耗
  • 使用 Java 工具,而不是操作系统工具,因为它们提供了错误的观点。您正在寻找不存在的内存泄漏。查看 Java 将进程内存用于什么用途:spring.io/blog/2019/03/11/memory-footprint-of-the-jvm

标签: java spring spring-boot


【解决方案1】:

除了堆之外,您还有线程堆栈、元空间、JIT 代码缓存、本机共享库和堆外存储(直接分配)。

我将从线程堆栈开始:您的应用程序在峰值时产生了多少线程?默认情况下,每个线程可能会为其堆栈分配 1MB,具体取决于 Java 版本、平台等。如果有(比如说)300 个活动线程(空闲或非空闲),您将分配 300MB 的堆栈内存。

考虑将所有线程池设为固定大小(或至少提供合理的上限)。即使这被证明不是您观察到的问题的根本原因,它也会使应用行为更具确定性,并有助于您更好地隔离问题。

【讨论】:

  • 要找出你的 jvm 的默认堆栈大小(以千字节为单位)(如果你没有用-Xss 覆盖你的应用程序),运行java -XX:+PrintFlagsFinal -version | grep ThreadStackSize。要检查实时线程,请使用jstack
  • 我认为默认大小是 1024 kb。尝试运行 jstack。不得不为其引入 -F 标志。结果是:JVM version is 25.282-b08 Deadlock Detection: java.lang.RuntimeException: Unable to deduc type of thread from address 0x00007f9f1c034800 (expected type JavaThread, CompilerThread, ServiceThread, JvmtiAgentThread, or SurrogateLockerThread)
  • 错误信息很可能具有误导性。您需要以该进程的所有者身份运行jstack,以其他用户身份运行有时可能会导致此类暴行。
  • 不错,现在可以使用了。有点长的回复。最后有一行:'JNI global references: 2108"
  • jstack ${pid} | grep 'java.lang.Thread.State' | wc -l
【解决方案2】:

那么丢失的 300M 在哪里呢?

对此进行了大量研究,尤其是在尝试调整控制非堆的参数方面。这项研究的一个结果是memory calculator (binary)。

您会看到,在对可用内存量有硬性限制的 Docker 环境中,当 JVM 尝试分配比可用内存更多的内存时,它会崩溃。即使进行了所有研究,memory calculator 仍然有一个称为“head-room”的松弛选项 - 通常设置为总可用内存的 5% 到 10%,以防 JVM 决定无论如何都要获取更多内存(例如在密集垃圾期间收藏)。

除了“head-room”之外,memory calculator 还需要 4 个额外的输入参数来计算控制内存使用的 Java 选项。

  • total-memory - Spring Boot 应用程序至少 384 MB,从 512 MB 开始。
  • loaded-class-count - 最新的 Spring Boot 应用程序大约 19 000。这似乎随着每个 Spring 版本而增长。请注意,这是一个最大值:设置过低的值会导致各种奇怪的行为(有时会引发“OutOfMemory: non-heap”异常,但并非总是如此)。
  • thread-count - 40 表示“正常使用”的 Spring Boot Web 应用程序。
  • jvm-options - 请参阅下面的两个参数。

“算法”部分提到了可以调整的其他参数,我发现其中两个值得为每个应用程序研究并指定:

  • -Xss 设置为 256kb。除非您的应用程序具有非常深的堆栈(递归),否则每个线程从 1 MB 到 256kb 可以节省大量内存。
  • -XX:ReservedCodeCacheSize 设置为 64MB。 “CodeCache”使用高峰通常是在应用程序启动期间,从 192 MB 到 64 MB 节省了大量可用作堆的内存。在运行时具有大量活动代码的应用程序(例如具有大量端点的 Web 应用程序)可能需要更多“CodeCache”。如果“CodeCache”太低,您的应用程序将使用大量 CPU 而没有做太多事情(这也可以在启动过程中表现出来:如果“CodeCache”太低,您的应用程序可能需要很长时间才能启动)。 “CodeCache”被 JVM 报告为非堆内存区域,应该不难衡量。

memory calculator 的输出是一堆 Java 选项,它们都会影响 JVM 使用的内存。如果你真的想知道“缺失的 300M”在哪里,除了“Java Buildpack Memory Calculator v3”rationale之外,还要研究和研究这些选项。

# Memory calculator 4.2.0

$ ./java-buildpack-memory-calculator --total-memory 512M --loaded-class-count 19000 --thread-count 40 --head-room 5 --jvm-options "-Xss256k -XX:ReservedCodeCacheSize=64M"

-XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=121289K -Xmx290768K

# Combined JVM options to keep your total application memory usage under 512 MB:

-Xss256k -XX:ReservedCodeCacheSize=64M -XX:MaxDirectMemorySize=10M -XX:MaxMetaspaceSize=121289K -Xmx290768K

【讨论】:

    【解决方案3】:

    您可以使用“JProfiler”https://www.ej-technologies.com/products/jprofiler/overview.html 远程或本地监控正在运行的 java 应用程序的内存使用情况。

    【讨论】:

      【解决方案4】:

      如果您将 IntelliJ 用作 IDE 来解决 Spring Boot 应用程序的内存相关问题,则可以将“yourkit”与 IntelliJ 一起使用。我以前使用过它,它可以更好地了解应用程序。

      https://www.yourkit.com/docs/java/help/idea.jsp

      【讨论】:

        【解决方案5】:

        关于内存分析的有趣文章:https://www.baeldung.com/java-profilers

        【讨论】:

          【解决方案6】:

          我们可以通过这种方式查看spring boot app的内存消耗量。

          1. 将 spring boot 应用程序创建为 .jar 文件并使用 java -jar springboot-example.jar 执行它

          2. 现在打开 CMD 并输入 jconsole 并回车。

            注意 :- 在打开 jconsole 之前,您需要运行 .jar 文件

          3. 现在您可以看到如下所示的窗口,它将显示以前在 本地进程 部分中运行的应用程序。

          4. 选择springboot-example.jar,点击下方connect按钮。

          5. 之后会出现以下提示并给出不安全的连接选项。

          6. 最后您可以看到 OverView 下方(堆内存、线程...)。

          【讨论】:

            猜你喜欢
            • 2018-05-05
            • 1970-01-01
            • 2019-01-16
            • 2011-02-26
            • 2020-01-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多