Nic*_*aux 95 java linux memory jvm docker
对于我的应用程序,Java进程使用的内存远远超过堆大小.
容器正在运行的系统开始出现内存问题,因为容器占用的内存比堆大小多得多.
堆大小设置为128 MB(-Xmx128m -Xms128m),而容器最多占用1 GB内存.在正常情况下,它需要500MB.如果docker容器具有以下限制(例如mem_limit=mem_limit=400MB),则该进程被OS的内存不足杀死所杀死.
你能解释为什么Java进程使用比堆更多的内存吗?如何正确调整Docker内存限制?有没有办法减少Java进程的堆外内存占用?
我使用JVM中的本机内存跟踪命令收集有关该问题的一些详细信息.
从主机系统,我获得容器使用的内存.
$ docker stats --no-stream 9afcb62a26c8
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
9afcb62a26c8 xx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.0acbb46bb6fe3ae1b1c99aff3a6073bb7b7ecf85 0.93% 461MiB / 9.744GiB 4.62% 286MB / 7.92MB 157MB / 2.66GB 57
Run Code Online (Sandbox Code Playgroud)
从容器内部,我获得进程使用的内存.
$ ps -p 71 -o pcpu,rss,size,vsize
%CPU RSS SIZE VSZ
11.2 486040 580860 3814600
Run Code Online (Sandbox Code Playgroud)
$ jcmd 71 VM.native_memory
71:
Native Memory Tracking:
Total: reserved=1631932KB, committed=367400KB
- Java Heap (reserved=131072KB, committed=131072KB)
(mmap: reserved=131072KB, committed=131072KB)
- Class (reserved=1120142KB, committed=79830KB)
(classes #15267)
( instance classes #14230, array classes #1037)
(malloc=1934KB #32977)
(mmap: reserved=1118208KB, committed=77896KB)
( Metadata: )
( reserved=69632KB, committed=68272KB)
( used=66725KB)
( free=1547KB)
( waste=0KB =0.00%)
( Class space:)
( reserved=1048576KB, committed=9624KB)
( used=8939KB)
( free=685KB)
( waste=0KB =0.00%)
- Thread (reserved=24786KB, committed=5294KB)
(thread #56)
(stack: reserved=24500KB, committed=5008KB)
(malloc=198KB #293)
(arena=88KB #110)
- Code (reserved=250635KB, committed=45907KB)
(malloc=2947KB #13459)
(mmap: reserved=247688KB, committed=42960KB)
- GC (reserved=48091KB, committed=48091KB)
(malloc=10439KB #18634)
(mmap: reserved=37652KB, committed=37652KB)
- Compiler (reserved=358KB, committed=358KB)
(malloc=249KB #1450)
(arena=109KB #5)
- Internal (reserved=1165KB, committed=1165KB)
(malloc=1125KB #3363)
(mmap: reserved=40KB, committed=40KB)
- Other (reserved=16696KB, committed=16696KB)
(malloc=16696KB #35)
- Symbol (reserved=15277KB, committed=15277KB)
(malloc=13543KB #180850)
(arena=1734KB #1)
- Native Memory Tracking (reserved=4436KB, committed=4436KB)
(malloc=378KB #5359)
(tracking overhead=4058KB)
- Shared class space (reserved=17144KB, committed=17144KB)
(mmap: reserved=17144KB, committed=17144KB)
- Arena Chunk (reserved=1850KB, committed=1850KB)
(malloc=1850KB)
- Logging (reserved=4KB, committed=4KB)
(malloc=4KB #179)
- Arguments (reserved=19KB, committed=19KB)
(malloc=19KB #512)
- Module (reserved=258KB, committed=258KB)
(malloc=258KB #2356)
$ cat /proc/71/smaps | grep Rss | cut -d: -f2 | tr -d " " | cut -f1 -dk | sort -n | awk '{ sum += $1 } END { print sum }'
491080
Run Code Online (Sandbox Code Playgroud)
该应用程序是一个Web服务器,使用Jetty/Jersey/CDI捆绑在一个36 MB的脂肪中.
使用以下版本的OS和Java(在容器内).Docker镜像基于openjdk:11-jre-slim.
$ java -version
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment (build 11+28-Debian-1)
OpenJDK 64-Bit Server VM (build 11+28-Debian-1, mixed mode, sharing)
$ uname -a
Linux service1 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 GNU/Linux
Run Code Online (Sandbox Code Playgroud)
https://gist.github.com/prasanthj/48e7063cac88eb396bc9961fb3149b58
apa*_*gin 163
Java进程使用的虚拟内存远远超出了Java堆.您知道,JVM包含许多子系统:垃圾收集器,类加载,JIT编译器等,所有这些子系统都需要一定量的RAM才能运行.
JVM不是RAM的唯一消费者.本机库(包括标准Java类库)也可以分配本机内存.而本机内存跟踪甚至无法看到这一点.Java应用程序本身也可以通过直接ByteBuffers使用堆外内存.
那么什么需要Java进程中的内存?
Java堆
最明显的部分.这是Java对象所在的位置.堆占用了-Xmx大量的内存.
垃圾收集器
GC结构和算法需要额外的内存用于堆管理.这些结构是Mark Bitmap,Mark Stack(用于遍历对象图),Remembered Sets(用于记录区域间引用)等.其中一些是直接可调的,例如-XX:MarkStackSizeMax,其他一些依赖于堆布局,例如,较大的是G1区域(-XX:G1HeapRegionSize),较小的是记忆集.
GC内存开销因GC算法而异.-XX:+UseSerialGC并且-XX:+UseShenandoahGC开销最小.G1或CMS可以轻松使用总堆大小的10%左右.
代码缓存
包含动态生成的代码:JIT编译的方法,解释器和运行时存根.它的大小受限于-XX:ReservedCodeCacheSize(默认为240M).关闭-XX:-TieredCompilation以减少编译代码的数量,从而减少代码缓存的使用.
编译器
JIT编译器本身也需要内存来完成它的工作.通过关闭分层编译或减少编译器线程的数量,可以再次减少这种情况:-XX:CICompilerCount.
类加载
类元数据(方法字节码,符号,常量池,注释等)存储在称为Metaspace的堆外区域中.加载的类越多 - 使用的元空间就越多.总使用量可以受限-XX:MaxMetaspaceSize(默认为无限制)和 -XX:CompressedClassSpaceSize(默认为1G).
符号表
JVM的两个主要哈希表:Symbol表包含名称,签名,标识符等,String表包含对实习字符串的引用.如果本机内存跟踪指示String表占用大量内存,则可能意味着应用程序过度调用String.intern.
主题
线程堆栈也负责占用RAM.堆栈大小由-Xss.每个线程的默认值是1M,但幸运的是事情并没有那么糟糕.操作系统懒惰地分配内存页面,即在第一次使用时,因此实际内存使用量将低得多(通常每个线程堆栈80-200 KB).我编写了一个脚本来估计RSS有多少属于Java线程堆栈.
还有其他JVM部件可以分配本机内存,但它们通常不会在总内存消耗中发挥重要作用.
应用程序可以通过调用显式请求堆外内存ByteBuffer.allocateDirect.默认的堆外限制等于-Xmx,但可以覆盖它-XX:MaxDirectMemorySize.Direct ByteBuffers包含在OtherNMT输出部分(或InternalJDK 11之前).
通过JMX可以看到使用的直接内存量,例如在JConsole或Java Mission Control中:
除了直接的ByteBuffers,还可以有MappedByteBuffers- 映射到进程虚拟内存的文件.NMT不跟踪它们,但MappedByteBuffers也可以占用物理内存.而且没有一种简单的方法来限制它们可以承受多少.您可以通过查看进程内存映射来查看实际用法:pmap -x <pid>
Address Kbytes RSS Dirty Mode Mapping
...
00007f2b3e557000 39592 32956 0 r--s- some-file-17405-Index.db
00007f2b40c01000 39600 33092 0 r--s- some-file-17404-Index.db
^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
加载的JNI代码System.loadLibrary可以根据需要分配尽可能多的堆外内存,而无需JVM端的控制.这也涉及标准的Java类库.特别是,未封闭的Java资源可能成为本机内存泄漏的来源.典型的例子是ZipInputStream或DirectoryStream.
JVMTI代理,特别是jdwp调试代理 - 也可能导致过多的内存消耗.
此答案描述了如何使用async-profiler配置本机内存分配.
进程通常直接从OS(通过mmap系统调用)或使用malloc- 标准libc分配器请求本机内存.反过来,malloc要求OS使用大块内存mmap,然后根据自己的分配算法管理这些块.问题是 - 该算法可能导致碎片和过多的虚拟内存使用.
jemalloc,替代分配器,通常看起来比常规libc更智能malloc,因此切换到jemalloc可能导致更小的空闲.
无法保证估计Java进程的完全内存使用量,因为有太多因素需要考虑.
Total memory = Heap + Code Cache + Metaspace + Symbol tables +
Other JVM structures + Thread stacks +
Direct buffers + Mapped files +
Native Libraries + Malloc overhead + ...
Run Code Online (Sandbox Code Playgroud)
可以通过JVM标志缩小或限制某些内存区域(如代码缓存),但许多其他内存区域完全不受JVM控制.
设置Docker限制的一种可能方法是在进程的"正常"状态下观察实际内存使用情况.有研究Java内存消耗问题的工具和技术:Native Memory Tracking,pmap,jemalloc,async-profiler.
Jan*_*raj 13
https://developers.redhat.com/blog/2017/04/04/openjdk-and-containers/:
为什么当我指定-Xmx = 1g时,我的JVM占用的内存超过1GB的内存?
指定-Xmx = 1g告诉JVM分配1gb堆.它没有告诉JVM将其整个内存使用量限制为1GB.有卡表,代码缓存和各种其他的堆外数据结构.用于指定总内存使用量的参数是-XX:MaxRAM.请注意,使用-XX:MaxRam = 500m时,您的堆将大约为250mb.
Java看到主机内存大小,并且不知道任何容器内存限制.它不会产生内存压力,因此GC也不需要释放已用内存.我希望XX:MaxRAM能帮助您减少内存占用.最终,你可以调整GC配置(-XX:MinHeapFreeRatio,-XX:MaxHeapFreeRatio,...)
有许多类型的内存指标.Docker似乎报告了RSS内存大小,这可能与报告的"已提交"内存不同jcmd(旧版本的Docker报告RSS +缓存作为内存使用情况).良好的讨论和链接:在Docker容器中运行的JVM的驻留集大小(RSS)和Java总提交内存(NMT)之间的差异
(RSS)内存也可以被容器中的其他一些实用程序吃掉 - shell,进程管理器......我们不知道容器中还有什么运行,以及如何在容器中启动进程.
Nic*_*aux 12
内存的详细使用情况由 Native Memory Tracking (NMT) 详细信息(主要是代码元数据和垃圾收集器)提供。除此之外,Java 编译器和优化器 C1/C2 消耗了摘要中未报告的内存。
使用 JVM 标志可以减少内存占用(但有影响)。
Docker 容器大小必须通过测试应用程序的预期负载来完成。
所述共享类空间可以在容器内被禁用,因为类不会被另一个JVM进程共享。可以使用以下标志。它将删除共享类空间 (17MB)。
-Xshare:off
Run Code Online (Sandbox Code Playgroud)
所述垃圾收集器串行具有垃圾收集处理期间,在较长的暂停时间成本最小的存储器占用(参见在一个画面GC之间阿列克谢Shipilëv比较)。可以使用以下标志启用它。它最多可以节省已使用的 GC 空间 (48MB)。
-XX:+UseSerialGC
Run Code Online (Sandbox Code Playgroud)
所述C2编译器可以用下面的标志被禁用,以减少用于决定是否优化与否的方法分析数据。
-XX:+TieredCompilation -XX:TieredStopAtLevel=1
Run Code Online (Sandbox Code Playgroud)
代码空间减少了 20MB。而且JVM外的内存减少了80MB(NMT空间和RSS空间的区别)。优化编译器 C2 需要 100MB。
的C1和C2的编译器可以用下面的标志被禁用。
-Xint
Run Code Online (Sandbox Code Playgroud)
JVM 外部的内存现在低于总提交空间。代码空间减少了 43MB。请注意,这会对应用程序的性能产生重大影响。禁用 C1 和 C2 编译器可减少 170 MB 使用的内存。
使用Graal VM 编译器(替代 C2)可以减少内存占用。它增加了 20MB 的代码内存空间,减少了 60MB 的外部 JVM 内存。
本文为JVM Java的内存管理提供了一些相关信息的不同的存储空间。Oracle 在Native Memory Tracking 文档中提供了一些详细信息。有关高级编译策略和禁用 C2 中编译级别的更多详细信息,将代码缓存大小减少 5 倍。关于为什么 JVM 报告的提交内存比 Linux 进程驻留集大小多的一些细节?当两个编译器都被禁用时。
| 归档时间: |
|
| 查看次数: |
14992 次 |
| 最近记录: |