10

我有一个 Tomcat webapp,它代表客户端执行一些漂亮的内存和 CPU 密集型任务。这是正常的,是所需的功能。但是,当我运行 Tomcat 时,内存使用量会随着时间的推移飙升至 4.0GB 以上,此时我通常会终止该进程,因为它会扰乱我的开发机器上运行的所有其他东西:

在此处输入图像描述

我以为我无意中在代码中引入了内存泄漏,但在使用 VisualVM 检查后,我看到了一个不同的故事:

在此处输入图像描述

VisualVM 显示堆占用了大约 GB 的 RAM,这是我设置的CATALINA_OPTS="-Xms256m -Xmx1024"

为什么我的系统认为这个进程占用了大量内存,而根据 VisualVM,它几乎没有占用任何内存?


在进一步嗅探之后,我注意到如果多个作业同时在应用程序中运行,内存不会被释放。但是,如果我等待每个作业完成,然后再将另一个作业提交给我的BlockingQueue服务ExecutorService,那么内存将被有效地回收。我该如何调试呢?为什么垃圾收集/内存重用会有所不同?

4

2 回答 2

11

您无法控制要控制的内容,-Xmx仅控制 Java Heap,它不控制JVM 对本机内存的消耗,根据实现的不同,其消耗完全不同。VisualVM 仅向您显示堆正在消耗的内容,它没有显示整个 JVM 作为本机内存消耗的 OS 进程。您必须使用操作系统级别的工具才能看到这一点,它们会报告完全不同的数字,通常比 VisualVM 报告的任何数据都要大得多,因为 JVM 以完全不同的方式使用本机内存

来自以下文章感谢内存(了解 JVM 如何在 Windows 和 Linux 上使用本机内存)

维护堆和垃圾收集器使用您无法控制的本机内存。

需要更多的本机内存来维护维护 Java 堆的内存管理系统的状态。收集垃圾时,必须分配数据结构来跟踪空闲存储和记录进度。这些数据结构的确切大小和性质因实现而异,但许多与堆的大小成正比。

javac并且 JIT 编译器像使用本机内存一样

字节码编译使用本机内存(与 gcc 等静态编译器需要内存才能运行的方式相同),但 JIT 的输入(字节码)和输出(可执行代码)也必须存储在本机内存中。包含许多 JIT 编译方法的 Java 应用程序比较小的应用程序使用更多的本机内存。

然后你有使用本机内存的类加载器

Java 应用程序由定义对象结构和方法逻辑的类组成。它们还使用 Java 运行时类库中的类(例如 java.lang.String),并且可能使用第三方库。只要使用这些类,就需要将它们存储在内存中。类的存储方式因实现而异。

我什至不会开始引用有关线程的部分,我认为您的想法是, -Xmx它无法控制您认为它控制的内容,它控制 JVM 堆,并非所有内容都进入 JVM 堆,并且堆占用更多您指定用于管理和簿记的本机内存。

简单明了,JVM 使用的内存比-Xms-Xmx和其他命令行参数中提供的更多。

这是一篇关于 JVM 如何分配和管理内存的非常详细的文章,它并不像您在问题中的假设所期望的那么简单,值得全面阅读。

许多实现中的 ThreadStack 大小都有最小限制,这些限制因操作系统和有时 JVM 版本而异;如果您将限制设置为低于 JVM 或操作系统的本机操作系统限制(有时必须设置 *nix 上的 ulimit),则会忽略线程堆栈设置。其他命令行选项的工作方式相同,当提供的值太小时,默认为更高的值。不要假设传入的所有值都代表实际使用的值。

类加载器和 Tomcat 不止一个,它们会占用大量不易记录的内存。JIT 会占用大量内存,以空间换时间,这在大多数情况下是一个很好的权衡。

于 2012-08-02T00:40:21.493 回答
0

您还应该检查 CPU 使用率和垃圾收集器。垃圾收集
可能会暂停并且 CPU gc 消耗会进一步减慢您的机器。

于 2012-08-02T00:46:41.777 回答