关于 Java 应用的 线程栈内存大小

对于 Java 应用, 对于内存的占用, 主要是Java堆, 通常我们为了性能考虑, 会对堆设置最大值, 否则 JVM 就会根据它锁了解的系统参数, 设置一个可能的最大值. 除了堆之外, 其实还有好几部分, 也可能占用不少内存. 比如元数据区(老年代), 线程栈, 编译后的代码, GC 程序, 编译器, JVM符号表之类的. 这里可以通过添加启动参数: -XX:NativeMemoryTracking=summary or -XX:NativeMemoryTracking=detail 来追踪原生内存的占用. 比如:
nmt.png

比如上面的截图中, kiyomi 看到 Thread 部分 reserve 了 1052M, 已经提交占用了 1052M, 查看 thread dump, 发现现在大概有 1003 个线程, 基本每个线程占用 1M.

另外我们知道, 可以使用 Xss 参数设置每个线程栈的大小, 比如:
-Xss1m
-Xss1024k
-Xss1048576
上面的参数等同于:
-XX:ThreadStackSize

在 Linux 64 bit 上, 默认是 1MB. 从 Oracle 的官方文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
可以看到具体的说明.

从上面的文档可以看出, 每个栈在 64bit 的 Linux 上使用 1M, 可是我从另外一个机器上看到大概有 20K 个线程, 可是占用的内存并不是 20G. 于是我们看到 对于 IBM JDK 和 OpenJ9 JDK 它们都用 3 个参数:

  1. -Xiss<size> Set initial Java thread stack size 2 KB
  2. -Xss<size> Set maximum Java thread stack size 320 KB (31/32-bit); 1024 KB (64-bit)
  3. -Xssi<size> Set Java thread stack size increment 16 KB
    这里它们对 Xss 的解读是最大的可用栈大小. 所以, 就有个疑问, 对于 HotSpot JDK, 是每个栈必须占用 1M, 还是这也是最大值???

更新:
根据这个 Bug 描述: https://bugs.openjdk.java.net/browse/JDK-8191369
应该是最大值, 之前的 NMT 简单的把 reserved 和 commited 置成 线程数 * Xss. 其实真正的 committed 可能并么有这么多, 虽然声明使用 1M 虚拟内存, 可能实际使用的物理内存页比较少. 所以 commited 比较少.

比如下面这个例子, 该 JVM 在一个 container 里面, 设置的最大可用内存是 16G, 已经使用 13.58G. 当前线程数是 11266 个, 堆大小设置的是 8G, 可以看到 Thread 通过 NMT 显示的是大概 10G, 总的 commit 已经大约 20G. 其实这个 Thread 占用的都是根据线程数 * Xss 算出来的, 都是根据最大值算出来的, 所以其实最后算出的 commit 是不准的. 根据 top 可得到该进程的 RSS 大概是 10G. 所以可以推算 Thread 占用少于 2 个 G.

nmt1.png

修复之后的版本:
newNMT.png

标签: none

添加新评论