我的 JVM 内存里有什么 | Mendix

跳到主要内容

我的 JVM 内存里有什么

“ Mendix 运行时执行在建模器中创建的模型。它向客户端提供页面并执行微流、调用 Web 服务、生成文档、与数据库通信等等。”

此 Mendix 运行时是用 Java 和 Scala 编写的程序,它使用 Java 虚拟机 (JVM)。运行时,它会占用操作系统中的一定量系统内存。

我的 JVM 内存里有什么?

让我们看一下显示 JVM 内存使用情况的可能图表:

seemright_mxruntime_jvmheap-day

在此图中,我们看到一个具有 512 MB Java 堆空间的应用程序。这是主要由以下程序使用的内存空间: Mendix 应用程序本身。如果您在微流中执行检索对象,则从数据库读取的对象将保存在内存中。

此 Mendix 运行时还使用此空间来存储已登录会话的 Java 对象、将数据库中的原始数据转换为可在微流内使用的对象或可直接在线转发的对象(如果数据必须在 Web 浏览器的数据网格中显示)。您是否知道甚至您的微流本身也是这里的对象? Mendix 运行时是一个解释器,它还在其内存中存储了建模应用程序的副本。

该内存空间的不同部分由 JVM 垃圾收集器管理。(另请参阅 JVM 堆文档)

前面显示的示例实际上是一个根本没有被使用的应用程序,但可能有一些预定的事件,这会导致创建一堆对象,这些对象可以在创建之后不久被垃圾收集。


JVM 并不孤单……

现在,这个 Java 内存也必须适合操作系统本身的系统内存。如果你正在使用 Mendix 云,您可以在我们的部署门户的监控部分查看这些图表。

这是显示操作系统内存实际使用情况的图表。这是一个所谓的 appnode,专用于运行单个环境的应用程序进程 Mendix 应用程序(因此,可以是测试、验收或生产环境)。

似乎正确的记忆日

看起来不错,不是吗?大约有 500MB 正在使用,所以这肯定是我们上面看到的 Java 堆。数据库在另一个虚拟机上运行,​​因此这里看不到数据库使用情况。显然 Mendix 配置了一个具有 1024MB 内存的 Linux 虚拟机来适应您的 Java 进程。


等等,什么?

现在,让我们看另一个例子:

wait_what_mxruntime_jvmheap-day

等待记忆日

等一下……为什么操作系统使用的内存超过 900MB,而不是应该使用的 512MB? Mendix 秘密地与我们的应用程序一起运行一些其他进程以利用原本未使用的内存?

不,事实并非如此。真的。🙂 好吧,好吧……除了应用程序本身之外,还有一些额外的进程始终在运行,例如两个监控代理,一个用于向趋势系统提供数据(以生成这些图表),另一个用于向警报系统提供数据。此外,还有一个进程可以在您按下部署门户上的停止或启动按钮时从部署门户收到信号,从而停止和启动您的应用程序。

但所有这些也都出现在第一个示例中。那么,这里发生了什么……?

我们来看看 ps 命令的输出,它可以显示服务器上每个进程占用了多少内存:

 

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
xxxxx    19137  1.8 84.7 1248796 864696 ?      Sl    2014 1177:33 java -Dfile.encoding=UTF-8 -XX:MaxPermSize=128M -Xmx512M -Xms512M -Djava.io.tmp...

 

是的,java 进程确实消耗了 864696 kB 的内存。


何必?

我们为什么要担心这个?毕竟,即使这个 1024MB 的 Java 进程不知何故神奇地增长到 512MB,该进程仍然可以容纳在可用的 844MB 内存中。

事实上,这种行为会导致一些问题,因为它会阻止服务器上不时启动的其他程序运行。

我们会发布版本并进行安全更新……

At Mendix,我们会定期(可能比您想象的要频繁得多)对我们的托管平台进行更改。每周,甚至更常见的是每周几次,我们对整个托管平台进行小幅更改。我们不会将新功能和错误修复堆积成每月一次的大型版本,而是在它们准备好投入生产时立即推出。

他们需要逃跑!

为了将更改部署到生产中,我们主要使用 puppet 和 Debian 软件包来更新运行客户项目的应用程序和数据库服务器上的软件。除此之外,还有一个名为无人值守升级的程序,它会在所有服务器上安装可用的安全更新。

因此,这个 Java 进程的过度发展可能会阻止上述任何情况的发生。不安装版本可能意味着您的应用程序可能会与部署系统的其余部分产生兼容性问题。错过安全更新是任何人都不希望发生的事情。

幸运的是,这个问题只出现在非常有限数量的环境中(目前占所有应用环境的 0.53%)。


Linux 内核来救援!

找出这里发生的情况并修复此行为的第一步是深入了解情况。

如果 Java 进程配置为使用 512MB 堆空间……那到底意味着什么?如上所示的 Java 堆图显示了用于存储 Java 代码中使用的实际对象的一部分内存。但是,要运行 Java 进程……我们需要启动 Java 程序本身。这个程序是否也必须存在于内存中?它位于这个 JVM 堆内存中吗?我们刚刚放在项目的用户库位置中的无数 jar 文件怎么办?它们会发生什么?

为了更好地理解这一点,最好有一个图表,不仅可以显示 Java 堆空间,还可以显示整个 Java 进程在操作系统中占用的内存空间。

...通过提供详细的内存使用信息

幸运的是,Linux 内核允许我们使用 proc 文件系统来检索单个进程所占用的内存地址空间的完整概览。我之前展示的示例 java 进程的 PID(程序标识符)是 19137。这意味着我可以在 Linux shell 提示符下发出以下命令,让 Linux 内核显示有关此进程内存分配的所有信息:cat /proc/19137/smaps

使用可用的 smaps 信息,可以对 JVM 进程内部发生的情况做出相当准确的有根据的猜测。

让我向您展示新的 JVM 进程内存使用情况图表,它属于本页显示的第一个应用程序图表:

似乎正确_mxruntime_jvm_process_memory-day

啊哈!毕竟占用了内存的 Java 堆不是 512MB!只有大约 230MB,另一部分似乎被 Java 对象堆之外分配的内存占用。其中一部分是永久代和代码缓存,用于存储 JVM 认为不需要垃圾回收的对象,因为只要应用程序在运行,这些对象就永远不会消失。另一部分是本机内存部分。

现在让我们看一下有问题的应用程序的图表:

wait_what_mxruntime_jvm_process_memory 天

在这种情况下,Java 堆被积极使用,因此它实际上是由操作系统分配的,而不仅仅是承诺在实际需要时可以使用。

这里的主要结论是,我们被误导了,以为实际的操作系统内存已被 JVM 对象堆完全占用,但事实并非如此。


当情况真的失控时……

为了总结这篇博文,这里有一个真正超越理智行为界限的情况的例子。

这里我们有一个 1024MB Java 堆配置,它从 512MB Java 对象堆升级到 1GB 对象堆:

whoah_mxruntime_jvmheap_tr10000-周

不幸的是,JVM 进程的内存使用量激增,我们不得不调整操作系统的大小来应对这种情况:

whoah_memory-week

新的 JVM 进程内存图显示了这里发生的情况:

whoah_mxruntime_jvm_process_memory_tr10000-周


要继续...

前几周对此进行的研究已经显示了许多可能的解决方案,可以解决日益严重的堆内存不足问题。在以后的博客文章中,我将详细说明这些解决方案。遇到上述发布和安全更新问题的客户应用程序可以期待来自 Mendix 关于我们如何在短期内解决这些问题。


它是如何运行的?

为了绘制这些图表,我查看了 Linux proc 文件系统中 smaps 文件中可用的所有信息。通过对一些位进行逆向工程并做出一些有根据的猜测,我创建了一个用于创建趋势的 m2ee 监控图配置扩展。检查 Linux 内核中的 smaps 信息的监控插件代码位于 m2ee-tools 源代码。 该 munin 插件文档 包含一些关于如何自行启用此插件的提示(如果您愿意)。

选择你的语言