【问题标题】:Unexplained extra memory consumed by docker running java processdocker运行java进程消耗的无法解释的额外内存
【发布时间】:2019-03-07 13:13:07
【问题描述】:

我们有什么:

  • 在 docker 容器中运行的 Java 应用程序
  • 未调用本机代码,未启动处理程序,未引用 DLL / .so 文件。
  • JVM 参数:-Xmx256m -XX:NativeMemoryTracking=summary
  • Docker 硬内存限制设置为768m
  • JVM 看起来很正常(正常的 GC 周期,没有内存泄漏,没有 OOM)
  • Docker 内存不断增长,直到达到硬限制 (768m) 导致终止并重新启动容器。

问题:

  • 为什么 docker stats 内存持续增长(导致每天都达到硬内存限制),尽管 JVM 似乎在其限制范围内运行。

  • 对于其他微服务,我们看不到这种行为

JVM

在 JVM 方面,我们没有注意到任何特别之处:

Docker 统计输出:

492.8MiB / 768MiB     64.17%              

[ec2-user@ip-10-180-28-222 ~]$ docker exec 34d7 jcmd 1 VM.native_memory summary
1:

Native Memory Tracking:

Total: reserved=1731355KB, committed=472227KB
-                 Java Heap (reserved=262144KB, committed=262144KB)
                            (mmap: reserved=262144KB, committed=262144KB)

-                     Class (reserved=1131805KB, committed=92829KB)
                            (classes #16224)
                            (malloc=7453KB #20996)
                            (mmap: reserved=1124352KB, committed=85376KB)

-                    Thread (reserved=29932KB, committed=29932KB)
                            (thread #30)
                            (stack: reserved=29772KB, committed=29772KB)
                            (malloc=94KB #151)
                            (arena=66KB #55)

-                      Code (reserved=255659KB, committed=35507KB)
                            (malloc=6059KB #9814)
                            (mmap: reserved=249600KB, committed=29448KB)

-                        GC (reserved=15369KB, committed=15369KB)
                            (malloc=5785KB #547)
                            (mmap: reserved=9584KB, committed=9584KB)

-                  Compiler (reserved=190KB, committed=190KB)
                            (malloc=59KB #858)
                            (arena=131KB #6)

-                  Internal (reserved=7849KB, committed=7849KB)
                            (malloc=7817KB #18468)
                            (mmap: reserved=32KB, committed=32KB)

-                    Symbol (reserved=20018KB, committed=20018KB)
                            (malloc=17325KB #175818)
                            (arena=2693KB #1)

-    Native Memory Tracking (reserved=3558KB, committed=3558KB)
                            (malloc=10KB #120)
                            (tracking overhead=3548KB)

-               Arena Chunk (reserved=4830KB, committed=4830KB)
                            (malloc=4830KB)

运行约20小时后

649.6MiB / 768MiB     84.59%               

[ec2-user@ip-10-180-28-222 ~]$ docker exec 34d7 jcmd 1 VM.native_memory summary
1:

Native Memory Tracking:

Total: reserved=1741020KB, committed=510928KB
-                 Java Heap (reserved=262144KB, committed=262144KB)
                            (mmap: reserved=262144KB, committed=262144KB)

-                     Class (reserved=1138319KB, committed=100495KB)
                            (classes #16390)
                            (malloc=7823KB #30851)
                            (mmap: reserved=1130496KB, committed=92672KB)

-                    Thread (reserved=30996KB, committed=30996KB)
                            (thread #31)
                            (stack: reserved=30800KB, committed=30800KB)
                            (malloc=97KB #156)
                            (arena=99KB #57)

-                      Code (reserved=261330KB, committed=69062KB)
                            (malloc=11730KB #16047)
                            (mmap: reserved=249600KB, committed=57332KB)

-                        GC (reserved=15363KB, committed=15363KB)
                            (malloc=5779KB #334)
                            (mmap: reserved=9584KB, committed=9584KB)

-                  Compiler (reserved=223KB, committed=223KB)
                            (malloc=92KB #1246)
                            (arena=131KB #6)

-                  Internal (reserved=8358KB, committed=8358KB)
                            (malloc=8326KB #18561)
                            (mmap: reserved=32KB, committed=32KB)

-                    Symbol (reserved=20253KB, committed=20253KB)
                            (malloc=17527KB #177997)
                            (arena=2725KB #1)

-    Native Memory Tracking (reserved=3846KB, committed=3846KB)
                            (malloc=10KB #127)
                            (tracking overhead=3836KB)

-               Arena Chunk (reserved=188KB, committed=188KB)
                            (malloc=188KB)

观察

观察 20 小时后我们目前所知道的:

  • Docker 统计数据从492.8MiB 跃升至649.6MiB
  • JVM 提交的本机内存从472227KB 跳转到510928KB
  • docker stats 和 JVM 提交的本机内存之间的差距似乎越来越大。 (649.6MiB - 510928KB 在哪里,为什么会增长)
  • JVM 统计数据在此期间保持正常。

所以我不知道我还能在 JVM 端调试什么。我了解 java 需要的不仅仅是堆(因此需要本机内存跟踪),但 jvm 本机内存跟踪器报告的内容与 docker stats 看到的内容之间仍有大约 150m 的差距。我怎样才能更深入地了解该内存的去向?

【问题讨论】:

  • 你的 dockerfile 是什么样子的?
  • github.com/IxorTalk/ixortalk-authserver/blob/master/src/main/… 我们传递的 JAVA_OPTS 是由 cloudformation 模板设置的,但我们传递的唯一内容是 -Xmx256m(最近还有用于调试的 -XX:NativeMemoryTracking=summary
  • 您可能希望将此问题交叉发布到 DevOpsSE
  • 我认为这个答案stackoverflow.com/a/38670876/482811 应该可以帮助您了解差异并解决您的问题。请注意,您要检查的第一个指标是内部 Java 堆,它是 Java 应用程序使用的总内存的(通常很大)一部分。因此,确实 Java 堆大小可能总是低于您的限制,但它并没有告诉您太多其他 Java 内存使用情况:GC、堆栈、堆外映射的内存等。
  • 在本机内存旁边,您可能还希望使用以下标志运行 jcmd:VM.stringtable / VM.symboltable。

标签: java docker memory


【解决方案1】:

JVM 报告的内存并不是一切。

JVM 的内存占用

那么,是什么导致了 JVM 内存占用?我们大多数人拥有 运行一个Java应用程序,知道如何设置最大堆空间。但 实际上,内存占用还有很多贡献:

  • 本机 JRE
  • 烫发/元空间
  • JIT 字节码
  • JNI
  • 蔚来
  • 线程

当我们要设置内存时,需要牢记这一点 Docker 容器的限制。并且还设置了容器内存 限制最大堆空间,可能还不够……

JVM 和 CPU

让我们简单看一下 JVM 是如何适应 运行它的节点上可用的处理器/内核。有 实际上是一些参数,默认情况下是基于初始化的 关于核心数。

  • ♯ JIT 编译器线程
  • ♯ 垃圾收集线程
  • ♯ 普通 fork-join 池中的线程 …

因此,如果 JVM 在 32 核节点上运行(并且其中一个没有覆盖 默认),JVM 将产生 32 个垃圾收集线程,32 个 JIT 编译器线程,…… source

为防止出现此问题,您应该使用 +UseContainerSupport(自 Java 10 中默认启用的 8u191 起可用)并且可能使用 -XX:MaxRAMPercentage=90.0 或更少,具体取决于您观察到的总已用内存。更多信息请参阅thisthat

我强烈推荐:"Nobody puts Java in the container: Ken Sipe" 来自 JavaZone

【讨论】:

    【解决方案2】:

    A.请仔细阅读 janisz 的答案并点击链接,对于在容器中或在 cgroups 下使用 Java 的人来说,它有很多非常重要的信息。

    B.主要问题是 JVM 没有看到容器的内存限制:它认为它拥有主机操作系统的全部空闲内存可以使用。当它尝试消耗超过 cgroup 限制允许的内存时,内核/docker 会因违反 cgroup 内存限制承诺而杀死容器。这就是 -XX:+UseContainerSupport 和旧的 -XX:+UseCGroupMemoryLimitForHeap 标志应该解决的问题:让 JVM 知道真正的限制是什么。

    附加信息

    -Xmx 标志不会限制 JVM(作为 Linux 进程)从操作系统中需要的所有内存。 JVM 本身的所有机制,包括 Java 堆栈、元空间、加载的代码等(如 janisz 的回答中所讨论的)也确实占用了内存空间。

    不幸的是,JVM 喜欢从操作系统中获取所需的内存,并且更喜欢占用更多的内存(如果它认为可用),然后重用现有的(并且可能是可释放的)内存。对这种行为的改进(即不假设 JVM 是系统中的唯一参与者)计划作为 Java 12 附带的新 G1 garbage collector 的一部分,但除非你有它为你工作,否则 JVM 将始终在它的内存使用,并且更愿意消耗所有可用的空闲内存,假设操作系统的唯一目的是为其正在运行的这个 JVM 提供服务。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-20
      相关资源
      最近更新 更多