Docker运行Java进程时消耗额外内存的原因未知。

26

我们拥有的:

  • 在 Docker 容器中运行的 Java 应用程序
  • 没有调用本地代码,没有启动进程,没有引用 DLL / .so 文件。
  • JVM 参数:-Xmx256m -XX:NativeMemoryTracking=summary
  • Docker 硬内存限制设置为 768m
  • JVM 似乎正常(正常的 GC 循环,没有内存泄漏,没有 OOM)
  • Docker 内存不断增长,直到达到硬限制(768m),导致容器被杀死并重新启动。

问题:

  • 为什么 Docker 统计的内存不断增长(每天都会达到硬内存限制),尽管 JVM 似乎在其限制范围内表现良好。

enter image description here

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

enter image description here

JVM

在 JVM 方面,我们没有注意到任何特殊情况:

enter image description here

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状态和JVM已分配本地内存之间的差距似乎在增加(649.6MiB - 510928KB在哪里以及为什么在增加)
  • 整个时间段内JVM状态保持正常

因此,我不知道我还能在JVM方面调试什么。我理解Java需要更多不只是堆的内容(因此需要追踪本地内存),但是JVM本地内存跟踪器报告的内容与docker状态之间仍存在约150m的差距。我该如何获得有关这些内存去向的更多信息?


你的Dockerfile长什么样子? - KeyMaker00
https://github.com/IxorTalk/ixortalk-authserver/blob/master/src/main/docker/Dockerfile 我们传递的 JAVA_OPTS 是由 CloudFormation 模板设置的,但我们传递的唯一参数是 -Xmx256m(最近添加了 -XX:NativeMemoryTracking=summary 用于调试)。 - ddewaele
你可能想要将这个问题同时发布到DevOpsSE上。 - Open Food Broker
1
我认为这个答案 https://dev59.com/wFkT5IYBdhLWcg3wcvCl#38670876 可以帮助你理解差异并可能解决你的问题。请注意,您正在检查的第一个指标是内部Java堆,它是Java应用程序使用的总内存的(通常很大的)一部分。因此,尽管Java堆大小可能始终低于您的限制,但它并不能告诉您有关其他Java内存使用情况的信息:GC、堆栈、在堆之外映射的内存等。 - Huygens
除了本地内存之外,您可能还想使用以下标志运行jcmd:VM.stringtable / VM.symboltable。 - Pieterjan Deconinck
2个回答

5

JVM报告的内存并不是全部。

JVM的内存占用

那么,是什么造成了JVM的内存占用?大多数运行Java应用程序的人都知道如何设置最大堆空间。但实际上有很多因素会影响内存占用:

  • 本地JRE
  • Perm / metaspace
  • JIT bytecode
  • JNI
  • NIO
  • 线程

这些需要牢记在心,特别是当我们想要在Docker容器中设置内存限制时。而且,将容器内存限制设置为最大堆空间可能是不够的...

JVM和CPU

让我们简要了解一下JVM如何调整到正在运行其节点上可用的处理器/核心数量。实际上有许多参数默认情况下基于核心计数进行初始化。

  • JIT编译器线程数
  • 垃圾回收线程数
  • 公共fork-join池中的线程数 ...

因此,如果JVM在32核节点上运行(并且没有覆盖默认值),则JVM将生成32个垃圾回收线程、32个JIT编译器线程,... source

为了防止这个问题,您应该使用+UseContainerSupport(自8u191以来可用,默认情况下在Java 10中启用),并可能使用-XX:MaxRAMPercentage = 90.0,或者根据您观察到的总使用内存少一些。更多信息请参见那个

我强烈推荐观看JavaZone的“Nobody puts Java in the container:Ken Sipe”视频。


1
A. 请仔细阅读janisz的回答并跟随链接,这对于在容器中使用Java或在cgroups下使用Java的人来说是非常重要的信息。
B. 主要问题是JVM看不到容器上的内存限制:它认为它有整个主机操作系统的可用空闲内存。当它尝试消耗超过cgroup限制允许的内存时,内核/ Docker会因违反cgroup内存限制承诺而杀死容器。这就是-XX:+UseContainerSupport和旧版-XX:+UseCGroupMemoryLimitForHeap标志所要解决的问题:让JVM知道真正的限制。

附加信息

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

很遗憾,JVM 喜欢从操作系统中获取尽可能多的内存,并且更喜欢获取更多的内存(如果认为可用)而不是重复使用现有的(可能可以释放的)内存。改进这种行为(即不假设 JVM 是系统中唯一的参与者)是 Java 12 中附带的 G1 垃圾收集器 的计划的一部分,但除非您已经对其进行了配置,否则 JVM 将始终增加其内存使用量,并且会倾向于消耗所有可用的空闲内存,假设操作系统的唯一目的就是为正在运行的 JVM 提供服务。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接