对于我的应用程序,java进程使用的内存远远大于堆大小。
运行容器的系统开始出现内存问题,因为容器占用的内存远远大于堆大小。
堆大小设置为128 mb( -Xmx128m -Xms128m
)而容器占用了1gb的内存。正常情况下需要500mb。如果docker容器的限制低于(例如。 mem_limit=mem_limit=400MB
)进程被操作系统的内存不足杀手杀死。
你能解释一下为什么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
从容器内部,我得到进程使用的内存。
$ ps -p 71 -o pcpu,rss,size,vsize
%CPU RSS SIZE VSZ
11.2 486040 580860 3814600
$ 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
该应用程序是一个web服务器,使用jetty/jersey/cdi捆绑在一个36mb的fat far中。
使用以下版本的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
https://gist.github.com/prasanthj/48e7063cac88eb396bc9961fb3149b58
3条答案
按热度按时间mm9b1k5b1#
java进程使用的虚拟内存远远超出了java堆。要知道,jvm包含许多子系统:垃圾收集器、类加载、jit编译器等等,所有这些子系统都需要一定数量的ram才能正常工作。
jvm不是ram的唯一消费者。本机库(包括标准java类库)也可以分配本机内存。本地内存跟踪甚至看不到这个。java应用程序本身也可以通过直接bytebuffers使用堆外内存。
那么,在java进程中什么需要内存呢?
jvm部件(主要通过本机内存跟踪显示)
java堆
最明显的部分。这就是java对象所在的地方。堆占用多达
-Xmx
内存量。垃圾收集器
gc结构和算法需要额外的内存来进行堆管理。这些结构包括标记位图、标记堆栈(用于遍历对象图)、记忆集(用于记录区域间引用)等。其中一些是直接可调的,例如。
-XX:MarkStackSizeMax
,其他依赖于堆布局,例如,较大的是g1区域(-XX:G1HeapRegionSize
),较小的是记忆集。gc内存开销因gc算法而异。
-XX:+UseSerialGC
以及-XX:+UseShenandoahGC
开销最小。g1或cms可能很容易使用总堆大小的10%左右。代码缓存
包含动态生成的代码:jit编译的方法、解释器和运行时存根。它的大小受到
-XX:ReservedCodeCacheSize
(默认为240米)。关掉-XX:-TieredCompilation
以减少编译代码的数量,从而减少代码缓存的使用。编译程序
jit编译器本身也需要内存来完成它的工作。通过关闭分层编译或减少编译器线程的数量,可以再次减少这种情况:
-XX:CICompilerCount
.类加载
类元数据(方法字节码、符号、常量池、注解等)存储在称为元空间的堆外区域中。加载的类越多,使用的元空间就越多。总使用量可能受到以下限制:
-XX:MaxMetaspaceSize
(默认情况下不受限制)和-XX:CompressedClassSpaceSize
(默认为1g)。符号表
jvm的两个主要哈希表:符号表包含名称、签名、标识符等,字符串表包含对内部字符串的引用。如果本机内存跟踪指示字符串表占用大量内存,则可能意味着应用程序调用过多
String.intern
.线程
线程堆栈还负责获取ram。堆栈大小由
-Xss
. 默认值是每个线程1m,但幸运的是情况并没有那么糟。操作系统会延迟分配内存页,即第一次使用时,因此实际内存使用量会低得多(通常每个线程堆栈80-200KB)。我编写了一个脚本来估计有多少rss属于java线程栈。还有其他jvm部分分配本机内存,但它们通常在总内存消耗中不起很大作用。
直接缓冲器
应用程序可以通过调用
ByteBuffer.allocateDirect
. 默认堆外限制等于-Xmx
,但可以用-XX:MaxDirectMemorySize
. 直接副缓冲器包括在Other
nmt输出段(或Internal
jdk 11之前)。使用的直接内存量可以通过jmx看到,例如在jconsole或java任务控制中:
除了直接的副缓冲器
MappedByteBuffers
-Map到进程的虚拟内存的文件。nmt不跟踪它们,但是mappedbytebuffer也可以占用物理内存。而且没有一个简单的方法来限制他们能吃多少。您可以通过查看进程内存Map来查看实际使用情况: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
^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
Total memory = Heap + Code Cache + Metaspace + Symbol tables +
Other JVM structures + Thread stacks +
Direct buffers + Mapped files +
Native Libraries + Malloc overhead + ...
hkmswyz62#
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、进程管理器。。。我们不知道容器中还运行着什么,以及如何在容器中启动进程。
7gs2gvoe3#
热释光;博士
内存的详细使用情况由本机内存跟踪(nmt)详细信息(主要是代码元数据和垃圾收集器)提供。除此之外,java编译器和优化器c1/c2会消耗摘要中未报告的内存。
使用jvm标志可以减少内存占用(但是会有影响)。
docker容器的大小必须通过应用程序预期负载的测试来完成。
各部件详图
可以在容器内禁用共享类空间,因为其他jvm进程不会共享这些类。可以使用以下标志。它将删除共享类空间(17mb)。
垃圾收集器序列具有最小的内存占用,但在垃圾收集处理过程中需要较长的暂停时间(请参阅aleksey shipil)ëv一张图片中gc之间的比较)。可以使用以下标志启用它。它可以节省最多使用的gc空间(48mb)。
可以使用以下标志禁用c2编译器,以减少用于决定是否优化方法的分析数据。
代码空间减少了20mb。此外,jvm外部的内存减少了80mb(nmt空间和rss空间之间的差异)。优化编译器c2需要100mb。
c1和c2编译器可以使用以下标志禁用。
jvm外部的内存现在低于提交的总空间。代码空间减少了43mb。注意,这对应用程序的性能有很大影响。禁用c1和c2编译器将减少170 mb的内存使用。
使用graalvm编译器(取代c2)可以减少内存占用。它增加了20mb的代码内存空间,减少了60mb的外部jvm内存。
java memory management for jvm一文提供了有关不同内存空间的一些相关信息。o