Java线程内存计算

6

是否可以为每个线程计算内存消耗? 假设我将任务分成4个线程,那么我想知道每个线程的内存消耗有多少? 我需要了解来自线程的平均和峰值内存使用情况。


1
我认为不可能计算每个线程的内存消耗。线程通常从共享内存中消耗,因此线程不拥有任何特定的内存。如果任何线程在变量作用域上工作,内存分析器将帮助您获取该信息。 - papaya
@papaya 我认为这是可能的。至少可以近似。如果你知道线程的作用,你就知道它创建了多少对象。可以创建对象的大小。静态实例和JVM开销的内存可以计算到第一个线程。 - AndiCover
@AndiCover 这是真的,就像我在我的回答中所说的那样,问题只会在线程开始关联/消耗共享内存时出现。除此之外,通过使用内存分析器,这是相当简单的。由于OP没有提到JVM版本等信息。这可能会有所帮助。https://docs.oracle.com/javase/6/docs/jre/api/management/extension/com/sun/management/ThreadMXBean.html#getThreadAllocatedBytes%28long%29 - papaya
@AndiCover,您可以记录它创建的对象及其内存占用情况,但这并不能说明线程使用的对象。正如papaya所说,堆内存是共享内存。第一个使用“foo”的线程将成为字符串对象的创建者,所有其他线程都将使用相同的对象,即使创建者已经停止使用它,甚至当创建者不存在时也是如此。线程之间可以进行任意对象交换,这些对象通常是持久存在的对象,而临时本地对象则是首先被垃圾回收的对象。 - Holger
@AndiCover,怎么做到的? - Edd
3个回答

6
作为其他人指出的,大多数对象都存储在上。该堆内存在线程之间共享。因此,无法确定哪些线程负责堆的大小。
但是,线程确实会获得自己的一块内存:栈。

栈大小

据我回忆,这是来自 Oracle 的 Ron Pressler 在2020年的演示中提到的...

传统线程

为每个线程分配一定量的内存用于其。由于当前基于 OpenJDK 的 Java 实现中的线程被映射为宿主操作系统的线程,因此栈大小任意设置为大约一兆。如果需要,可以分配更多内存,但不会减少。

虚拟线程

随着纤维(即“Project Loom”提出的虚拟线程)的出现,情况变得更加复杂。
Project Loom为Java并发功能增加了新的能力。作为其中的一部分,虚拟线程与主机操作系统线程(也称为平台/内核线程)进行多对一映射。JVM将管理这些虚拟线程,而不是操作系统,在代码块阻塞时“挂起”虚拟线程,以便通过分配给“真实”的平台/内核线程的执行时间运行另一个虚拟线程。该“真实”的平台/内核线程在CPU核心上实际完成工作的调度由主机操作系统控制,无论是否使用Project Loom(至少在基于OpenJDK的Java实现中)。
➥ 作为JVM对虚拟线程的管理的一部分,每个虚拟线程的堆栈将开始得小得多。每个堆栈将根据需要增长和缩小。
由于这种对CPU和内存的高效利用,虚拟线程的成本大大降低。因此我们可以运行更多的虚拟线程。即使在普通硬件上也可能有数百万个虚拟线程。

1
Project Loom 允许虚拟线程在任意 Executor 实现上进行安排,例如 ThreadPoolExecutor。因此,这是一种 多对多 的关系。 - Holger
非常感谢您的解释。现在我明白了Java如何管理线程的内存。 - Edd

2
总结我的评论,线程使用共享内存。因此,除了保留的堆栈内存(在jvm启动时设置),没有任何线程拥有自己的数据。

如果你关注线程在运行jvm时消耗的确切堆大小,可以使用内存分析器如visualvm查看线程创建的类和对象,并估算其消耗大小。

你还可以使用ThreadLocal变量来定义属于特定线程的对象。这也可以帮助你按线程基础获得确切的内存消耗。

你还可以查看ThreadMXBean,但这在最新的jvm中已不再可用。


我想我确切地说了那句话。请再读一遍? - papaya
是的,你说了。我错过了,是我的错误。 - Basil Bourque
非常感谢您的回答。我已经了解了线程使用的共享内存的概念。关于ThreadLocal,我该如何计算其内存消耗?如果我调用Runtime.getRuntime().freeMemory()到那个ThreadLocal,它会显示整体的空闲内存,对吗? - Edd

0
你可以使用本地内存跟踪来获取关于线程的一般信息,但我不确定如何获取每个线程的信息。
在启动应用程序时,在你的VM选项中添加-XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics
java -XX:+UnlockDiagnosticVMOptions -XX:NativeMemoryTracking=summary -XX:+PrintNMTStatistics -jar myjar.jar

现在,如果你运行:

$jps -l

查找您计算机上的Java进程,然后

$jcmd <PID> VM.native_memory

您将看到类似下面这样的内容:
Native Memory Tracking:

Total: reserved=2344374KB +4399KB, committed=567362KB +19659KB
...
- Thread (reserved=87894KB, committed=19126KB +756KB)
         (thread #159)
         (stack: reserved=87132KB, committed=18364KB +756KB)
         (malloc=577KB #956)
         (arena=184KB #316)
...

通过这些信息,您可以进行19126KB/159=120KB的计算,获取每个线程的平均承诺内存大小。但问题在于,这种方法无法提供每个线程的具体情况,有些线程可能较大,而其他线程可能较小。

您还可以尝试以下方法:

$jcmd <PID> VM.native_memory baseline
$jcmd <PID> VM.native_memory summary.diff

这将告诉您基准时间点和在高峰交通期间运行的差异。

需要注意的是:启用NMT会带来一些额外的开销。您可能需要权衡在启用此功能的情况下在PROD中运行应用程序的风险。


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