JVM内存使用失控

我有一个Tomcat webapp,它代表客户执行一些漂亮的内存和CPU密集型任务。 这是正常的,是所需的function。 但是,当我运行Tomcat时,内存使用量会随着时间的推移而猛增至4.0GB以上,此时我通常会杀死该进程,因为它会破坏我在开发计算机上运行的所有其他内容:

在此处输入图像描述

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

在此处输入图像描述

VisualVM将堆显示为占用大约一GB的RAM,这就是我将其设置为使用CATALINA_OPTS="-Xms256m -Xmx1024"

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


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

您无法控制要控制的内容-Xmx仅控制Java堆,它不控制JVM对本机内存的消耗,而JVM根据实现完全不同地使用。 VisualVM只显示Heap正在消耗的内容,它不会显示整个JVM作为本机内存消耗的操作系统进程。 您将不得不使用操作系统级工具来查看它,并且它们将报告完全不同的数字,通常比VisualVM报告的任何数字都大得多,因为JVM以完全不同的方式使用本机内存

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

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

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

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

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

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

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

我甚至不会开始引用关于Threads的部分,我认为你会认为-Xmx不能控制你认为它控制的是什么,它控制着JVM堆,而不是JVM堆中的所有内容,并且堆占用了更多的本机记忆,你指定的管理和簿记。

简单而简单的JVM使用的内存比-Xms-Xmx以及其他命令行参数中提供的内存更多。

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

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

类加载器和Tomcat有不止一个,占用了大量内存,而这些内存很难被记录下来。 JIT占用了大量的内存,随着时间的推移交易空间,这在大多数情况下是一个很好的交易。

您还应该检查CPU使用情况和垃圾收集器。
垃圾收集可能会暂停 ,CPU gc会进一步降低您的机器速度。