运行Java进程的docker占用了无法解释的额外内存

时间:2018-10-02 11:56:02

标签: java docker memory

我们所拥有的:

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

问题:

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

enter image description here

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

enter image description here

JVM

在JVM方面,我们没有发现任何特殊之处:

enter image description here

Docker stats输出:

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
  • 泊坞窗统计信息与JVM提交的本机内存之间的差距似乎正在扩大。 (649.6MiB - 510928KB在哪里,为什么会增长)
  • 在此期间,JVM统计信息保持正常。

所以我不知道我还能在JVM端调试什么。我知道Java不仅需要堆(因此需要进行本机内存跟踪),但是jvm本机内存跟踪器报告的内容与docker stats所看到的之间仍然存在约150m的差距。我如何才能获得有关内存运行方向的更多见解?

2 个答案:

答案 0 :(得分:1)

JVM报告的内存不足。

  

JVM的内存占用量

     

那么,什么导致JVM内存占用呢?我们大多数人   运行Java应用程序,知道如何设置最大堆空间。但   实际上,对内存占用的贡献更多:

     
      
  • 本地JRE
  •   
  • 烫发/元空间
  •   
  • JIT字节码
  •   
  • JNI
  •   
  • NIO
  •   
  • 线程
  •   
     

当我们要设置内存时,需要记住很多   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

答案 1 :(得分:0)

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

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

其他信息

-Xmx标志并没有限制JVM(作为Linux进程)需要操作系统提供的所有内存。 JVM本身的所有功能,包括Java堆栈,元空间,已加载的代码等(如janisz的答案所述)的确也占用内存空间。

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