Java使用的内存比Xmx参数设定的更多。

13

我有一个非常简单的Web服务器类(基于Java SE的HttpServer类)。

当我使用以下命令启动已编译的类并设置内存使用限制:

java -Xmx5m -Xss5m -Xrs -Xint -Xbatch Test

现在,如果我使用top命令检查内存,则指示执行我的类的Java进程使用了约31MB的驻留内存。

我想知道那30MB的内存用于什么?


2
仅供参考,按照现代标准,5兆字节是微不足道的内存量。整个JVM进程的30MB也是如此。另外,“-Xmx”选项仅指定最大堆大小。JVM也需要占用空间。 - Jim Garrison
2
-Xmx参数指定的是JVM堆的最大大小,而不是JVM使用的总内存大小。 - user207421
2
你为每个线程分配了5m的堆栈大小。问题应该是为什么它只使用31m内存... - Holger
3个回答

20

正如评论和答案所提到的,当测量JVM内存使用情况时,还有许多其他因素需要考虑。然而,我认为没有任何答案深入到足够的程度。

JVM内存概述

让我们直接回答"I was wondering what is that 30MB used for?"这个问题。为了解决这个问题,这里有一个简单的Java类:

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) throws Exception {
        System.out.println("Hello world!");
        Thread.sleep(10000); // wait 10 seconds so we can get memory usage
    }
}

现在使用堆限制编译并运行它:
$ nohup java -Xms2m -Xmx2m HelloWorld & # run in background
$ ps aux | awk 'NR==1; /[H]elloWorld/'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
chaospie  6204  6.0  0.1 2662860 23040 pts/2   Sl   19:15   0:00 java -Xms2m -Xmx2m HelloWorld

从上面的RSS(Resident Set Size,或进程使用的内存量)可以看出JVM进程正在使用大约23MB的内存。为了了解原因,让我们进行一些分析。获取良好概述的最快方法是打开NativeMemorytracking,使用jcmd工具的VM.native_memory命令。因此,让我们再次运行我们的应用程序:

$ nohup java -XX:NativeMemoryTracking=summary -Xms2M -Xmx2M HelloWorld &
[2] 6661
nohup: ignoring input and appending output to 'nohup.out'

$ ps aux | awk 'NR==1; /[H]elloWorld/'
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
chaospie  6661  5.0  0.1 2662860 23104 pts/2   Sl   19:21   0:00 java -XX:NativeMemoryTracking=summary -Xms2M -Xmx2M HelloWorld

$ jcmd 6661 VM.native_memory summary
6661:

Native Memory Tracking:

Total: reserved=1360145KB, committed=61177KB
-                 Java Heap (reserved=2048KB, committed=2048KB)
                            (mmap: reserved=2048KB, committed=2048KB)

-                     Class (reserved=1066093KB, committed=14189KB)
                            (classes #402)
                            (malloc=9325KB #146)
                            (mmap: reserved=1056768KB, committed=4864KB)

-                    Thread (reserved=20646KB, committed=20646KB)
                            (thread #21)
                            (stack: reserved=20560KB, committed=20560KB)
                            (malloc=62KB #110)
                            (arena=23KB #40)

-                      Code (reserved=249632KB, committed=2568KB)
                            (malloc=32KB #299)
                            (mmap: reserved=249600KB, committed=2536KB)

-                        GC (reserved=10467KB, committed=10467KB)
                            (malloc=10383KB #129)
                            (mmap: reserved=84KB, committed=84KB)

-                  Compiler (reserved=132KB, committed=132KB)
                            (malloc=1KB #21)
                            (arena=131KB #3)

-                  Internal (reserved=9453KB, committed=9453KB)
                            (malloc=9421KB #1402)
                            (mmap: reserved=32KB, committed=32KB)

-                    Symbol (reserved=1358KB, committed=1358KB)
                            (malloc=902KB #86)
                            (arena=456KB #1)

-    Native Memory Tracking (reserved=143KB, committed=143KB)
                            (malloc=86KB #1363)
                            (tracking overhead=57KB)

-               Arena Chunk (reserved=175KB, committed=175KB)
                            (malloc=175KB)

内存区域

让我们逐一分析1

  • Java Heap:这是堆 -
  • Class:这是Metaspace,假设您使用的是Java 8。
  • Thread:这显示线程数和线程的总内存使用情况(请注意,在此部分中使用的stack反映了Xss值乘以线程数,您可以通过java -XX:+PrintFlagsFinal -version | grep ThreadStackSize 获取默认的-Xss值)。
  • Code:代码缓存 - JIT(即时编译器)用于缓存已编译代码。
  • GC:垃圾回收器使用的空间。
  • Compiler:JIT生成代码时使用的空间。
  • Symbols:这是用于符号、字段名称、方法签名等的符号表。
  • Native Memory Tracking:本地内存跟踪器本身使用的内存。
  • Arena Chunk:这与malloc arenas 2有关。

不仅仅是堆!

保留、提交和RSS

请注意,每个区域都有一个committedreserved部分。简单来说,reserved是它可以增长到的大小,而committed是当前已经承诺使用的大小。例如,看一下Java Heap部分:Java Heap (reserved=2048KB, committed=2048KB)reserved是我们的-Xmx值,而committed则是我们的-Xms value值,在这种情况下,它们是相等的。
还要注意,总的committed大小并不反映RSS(或top中的RES列)报告的实际使用情况。它们之间的差异在于RSS显示了所有仍在物理内存中使用的内存页面的大小,而committed显示的是已经使用的内存,包括那些不在物理内存中的内存3
当然,这方面还有很多内容,但JVM和操作系统的内存管理是一个复杂的话题,因此我希望这至少能够回答您的问题。
  1. 请参阅 https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr022.html
  2. 从JVM本地内存跟踪文档(https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html#BABJGHDB)中:

Arena是使用malloc分配的一块内存。当退出作用域或离开代码区域时,会批量释放这些内存,这些块可以在其他子系统中重复使用以保存临时内存,例如预线程分配。Arena malloc策略确保没有内存泄漏。因此,Arena被整体跟踪而不是单个对象。一些初始内存无法跟踪。

  1. 深入讨论RSS,Reserved和Committed内存之间的差异将太过繁琐,操作系统内存管理是一个复杂的话题,但请参见this answer以获取良好的概述。

4
Java运行在虚拟机上,而不是直接在您的硬件上运行。这意味着这台机器需要自己的内存来运行。您允许程序使用的5MB可能意味着Java虚拟机(JVM)还使用了另外26MB的内存。

1

-Xmx5m仅用于堆内存,top命令会显示整个内存使用情况,包括JNI调用在进程中处理时使用的本地内存。


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