JVM进程如何分配内存?

16

我对JVM进程如何分配自己的内存还有一些不太明白的地方。就我所知,

RSS = Heap size + MetaSpace + OffHeap size

OffHeap由线程堆栈、直接缓冲区、映射文件(库和JAR文件)以及JVM代码本身组成;

目前我正在分析我的Java应用程序(Spring Boot + Infinispan),其 RSS为779M(它运行在Docker容器中,因此pid 1没问题):

[ root@daf5a5ae9bb7:/data ]$ ps -o rss,vsz,sz 1
RSS    VSZ    SZ
798324 6242160 1560540

根据 jvisualvm,堆大小为 374M enter image description here

元空间大小为89M
enter image description here

换句话说,我想解释一下 799M - (374M + 89M) = 316M 的 OffHeap 内存。

我的应用程序平均有 36个活动线程enter image description here

每个线程消耗 1M:

[ root@fac6d0dfbbb4:/data ]$ java -XX:+PrintFlagsFinal -version |grep ThreadStackSize    
intx CompilerThreadStackSize                   = 0
intx ThreadStackSize                           = 1024
intx VMThreadStackSize                         = 1024

所以,我们可以添加36M

应用程序仅在NIO中使用DirectBuffer。从JMX中我能看到它不会消耗大量资源-只有98K enter image description here

最后一步是映射的库和jar。但是根据pmap (完整输出)

[ root@daf5a5ae9bb7:/data ]$ pmap -x 1 | grep ".so.*" | awk '{ sum+=$3} END {print sum}'

12896K

root@daf5a5ae9bb7:/data ]$ pmap -x 1 | grep “.jar" | awk '{ sum+=$3} END {print sum}'

9720K

我们这里只有20M

因此,我们仍然需要解释 316M - (36M + 20M) = 260M :(

有人知道我错过了什么吗?


1
这里是JVM本身的所有共享库。尝试运行两个JVM,看看添加了多少。 - Peter Lawrey
2
对于这项出色的研究,点个赞! - fge
我完全同意@PeterLawrey的观点。通过执行“ldd /path/to/java”和“pmap PID”来检查Java正在运行哪些共享库,以便详细了解Java实际使用了什么。 - diginoise
它不能作为共享库,因为这是“堆大小”,堆大小是Java内存的一部分。 - loonytune
1
另外,正如您所看到的,“已使用”比实际堆大小要小得多。虚拟机喜欢占用大量内存以提高GC性能。即使是已使用的堆,其中很多可能是死对象,在执行完整GC时将被收集。 - loonytune
2
  • 代码缓存(用于动态生成的代码)
  • GC内部结构(如CardTables)
  • 由本地库分配的结构(I/O、网络等)
  • JVM和libs的.bss段
- apangin
1个回答

11

方法:

您可以使用Java HotSpot本机内存跟踪(NMT)

这可能会为您提供JVM分配的内存的确切列表,分为不同的区域堆、类、线程、代码、GC、编译器、内部、符号、内存跟踪、池化自由块未知

用法:

  • 您可以使用-XX:NativeMemoryTracking = summary启动应用程序。

  • 可以使用jcmd <pid> VM.native_memory summary观察当前堆的情况。

在哪里找到jcmd / pid

在Ubuntu上默认的OpedJDK安装中,可以在/usr/bin/jcmd找到它。

只需运行jcmd而不带任何参数,即可获取正在运行的Java应用程序列表。

user@pc:~$ /usr/bin/jcmd
5169 Main                       <-- 5169 is the pid

输出:

然后,您将收到有关您的堆的完整概述,类似于以下内容:

Total: 保留=664192KB,已提交=253120KB <--- Native Memory Tracking跟踪的总内存
  • Java Heap (保留=516096KB,已提交=204800KB) <--- Java堆

    (mmap: 保留=516096KB,已提交=204800KB)

  • Class (保留=6568KB,已提交=4140KB) <--- 类元数据

    (类 #665) <--- 已加载类的数量

    (malloc=424KB,#1000) <--- malloc'd 内存,#malloc的数量

    (mmap: 保留=6144KB,已提交=3716KB)

  • Thread (保留=6868KB,已提交=6868KB) (线程 #15) <--- 线程数量

    (stack: 保留=6780KB,已提交=6780KB) <--- 线程栈使用的内存

    (malloc=27KB,#66)

    (arena=61KB,#30) <--- 资源和句柄区域

  • Code (保留=102414KB,已提交=6314KB)

    (malloc=2574KB,#74316)

    (mmap: 保留=99840KB,已提交=3740KB)

  • GC (保留=26154KB,已提交=24938KB)

    (malloc=486KB,#110)

    (mmap: 保留=25668KB,已提交=24452KB)

  • Compiler (保留=106KB,已提交=106KB)

    (malloc=7KB,#90)

    (arena=99KB,#3)

  • Internal (保留=586KB,已提交=554KB)

    (malloc=554KB,#1677)

    (mmap: 保留=32KB,已提交=0KB)

  • Symbol (保留=906KB,已提交=906KB)

    (malloc=514KB,#2736)

    (arena=392KB,#1)

  • Memory Tracking (保留=3184KB,已提交=3184KB)

    (malloc=3184KB,#300)

  • Pooled Free Chunks (保留=1276KB,已提交=1276KB)

    (malloc=1276KB)

  • Unknown (保留=33KB,已提交=33KB)

    (arena=33KB,#1)

这提供了JVM使用的不同内存区域的详细概述,并显示了保留已提交的内存。

我不知道有一种技术可以给出更详细的内存消耗列表。

进一步阅读:

您还可以结合其他jcmd命令使用-XX:NativeMemoryTracking=detail。更详细的解释可以在Java平台标准版故障排除指南-2.6 jcmd实用程序中找到。您可以通过"jcmd <pid> help"检查可能的命令。


你的 jcmp 有拼写错误吗? - jmj
谢谢,我马上修复。 - Markus Weninger
谢谢您的回答!我尝试使用NMT并发现它非常有帮助...但我仍然有一个问题 :) NMT说我丢失的所有内存都属于“未知”部分。当我尝试分析详细信息时,我发现了以下内容:https://gist.github.com/krestjaninoff/a89ee990d94d8fc2917a 您对这个内存发生了什么有任何想法吗?它被CMS消耗了吗? - Michael
2
本版本中的NMT不跟踪第三方本地代码内存分配和JDK类库。 (c) 好的,问题已关闭 :) - Michael

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