在阅读此博客之前,我强烈建议您先阅读有关客户端状态的介绍: 国家艺术(第一部分).
在第 2 部分中,我们将深入探讨客户端状态下对象管理的工作原理。我们将处理高度抽象的概念,因此我建议您在阅读本文时随身携带一杯热饮和一些健脑零食(对于令人头晕目眩的对象管理想法,我自己更喜欢吃薯片)。
1.什么是垃圾收集?为什么需要垃圾收集?
您将在对应用程序进行建模时创建对象、提交、回滚和删除对象。您可以在 UI 中显示这些对象、将它们传递给微流和纳米流、修改它们或在自定义小部件中使用它们。您正在处理的一些对象是可持久的;有些则不是。非持久性对象始终存在于内存中,因此永远不会保存到数据库中。您也永远不能提交新的可持久性对象 — 这会使其实际上成为非持久性的,因为它存在于内存中。
在典型的用户会话期间,用户会花数小时使用应用程序,访问数十个页面,从而创建多个对象(可持久化或非持久化)。这些对象成为客户端状态的一部分,并占用浏览器内存中的空间,这可能会随着时间的推移降低应用程序的速度。作为 Mendix 开发人员,您不需要删除您在应用程序中创建但未提交的每个对象,并且您的应用程序仍将正常工作而不会降低性能。这怎么可能呢? Mendix 如何处理用户在十页前创建的不可持久化对象?如何处理对可持久化对象所做的更改?
垃圾收集机制就是这些问题的答案。

此 Mendix 客户端分析其状态中的对象和变化,如果发现它们不再需要,则将其删除。此过程的目标是最小化内存中的状态保留。确定 Mendix 应用程序是否需要对象取决于我在下面详述的一些标准。
2. 垃圾收集何时发生?
在我家附近,通常是星期二早上。 Mendix 应用程序,当您使用该应用程序时,垃圾收集会在后台进行。它会定期启动,分析所有对象及其状态变化,并删除它认为不再需要的对象或变化。
3. 我可以看到垃圾收集的过程吗?
您可以使用状态检查快捷方式 (Ctrl + Alt + G) 检查客户端状态,并可能看到垃圾收集。但是,您需要一点运气才能看到即将被收集的对象。通常,收集会在您看到之前发生。在这里您可以看到将被收集的对象,因为垃圾收集尚未启动:

只需几秒钟,这个物体就不再可见了。
您还可以启用特殊设置来查看垃圾回收时删除了哪些对象。为此,请添加新的 data 部分与 logCleanupStatistics: true财产 dojo配置 应用程序中配置的对象 主题/index.html 文件。您的代码应如下所示:
dojoConfig = {
baseUrl: "mxclientsystem/dojo/",
cacheBust: "{{cachebust}}",
rtlRedirect: "index-rtl.html",
data: {
"logCleanupStatistics": true
}
};
添加此部分后,每当发生垃圾收集时都会添加一条信息消息:

免责声明:上述配置不公开,如有变更或删除,恕不另行通知。
4.垃圾收集如何工作?
现在您知道应用程序中创建或加载的不必要的对象和更改已从客户端状态中删除,从而使您的应用程序能够继续快速运行。但这是如何实现的呢?
这个问题的概念性答案很简单: Mendix 客户端应该知道在任何时候是否有组件需要某个对象。如果不需要,它可以将其从状态中移除。而且由于 Mendix 对象可以通过关联引用一个或多个对象(或被引用), Mendix 客户端也需要跟踪依赖对象。
让我们深入探讨一下这些概念在 Mendix 顾客。
5.订阅
会员充值 是垃圾收集的关键。订阅系统是 Mendix 客户端,使用它,你可以在发生任何更改时请求通知 Mendix 对象或其属性。它看起来像是自定义小部件的简单 API,但实际上, Mendix 客户端经常在内部使用订阅。以下是一些内部使用示例:
- 每当数据视图加载一个对象时,它都会订阅已加载的对象,以便它可以接收有关特定情况的通知(例如,当对象被删除时)。
- 大多数输入小部件都依赖订阅来获取有关它们正在处理的对象的更新。这样,它们就可以在其他地方更改值时更新它们的值。Dojo 小部件通常手动订阅其对象。然而 可插入的小部件 不需要这样做,因为他们的订阅由 Mendix 顾客。
- 此 Mendix 客户端订阅页面上对象的条件可见性和可编辑性规则,因此当相关对象或属性发生变化时,它会重新评估条件。
- 带参数的文本小部件订阅其对象。如果模板值经过关联,文本小部件还会订阅所有中间对象,因此小部件知道它们应该在关联发生变化时进行更新。
垃圾收集机制将订阅视为输入,以标记应用程序中需要订阅的对象并且不应被收集 - 组件或小部件仍然使用或需要它。
此 Mendix 客户端还可以订阅在给定时刻未显示在 UI 中的对象,即使不使用通知回调也是如此。这是一种表明 Mendix 客户端需要这些对象。以下是此类行为的几个示例:
-
代表的对象 $当前用户 和 $当前会话 总是在 Mendix 客户端,因为只要用户使用该应用程序,它们就会存在,并且应用程序可能随时需要它们。
注:一 $当前会话 出于安全原因,对象永远不会被发送回客户端,但是 Mendix 客户端仍然订阅它。下面将解释这样做的原因。
-
数据网格的数据源微流可能返回比数据网格在单个页面上可以显示的对象更多的对象。这些对象将成为客户端状态的一部分。 Mendix 客户端订阅所有这些对象,以便垃圾收集不会意外地将它们从客户端状态中删除。
-
当带有参数的页面关闭时,垃圾回收可能会从客户端状态中删除参数对象(假设该对象不再需要)。这样的页面参数可能是非持久性对象,这意味着删除它会使其永远无法访问。但由于用户可以使用浏览器的后退按钮返回,因此需要将参数对象保持在状态中。这就是为什么 Mendix 客户端订阅了最后五页的页面参数对象:因此垃圾收集不会删除它们。
5.1 我能看到为什么对象被保留在该状态吗?
有时,你可以通过使用 Ctrl + Alt + G 快捷方式。在下面的示例中, 艺人 对象由当前页面上的小部件订阅。有时你可以看到 空 中的条目 订阅小部件 数组。这意味着它们要么从 Mendix 客户端内部或从自定义小部件:

所以,很明显,只要订阅的对象还处于订阅状态,就应该保持该状态。
这就是全部?
6.可达性的重要性
如前所述,仅保留订阅的对象是不够的。 Mendix 客户端需要考虑关联。这是一个非持久性的 Mendix 被调用的对象 对象 A 在客户端状态下:

由于没有被订阅,因此当发生垃圾收集时,该对象会被从状态中删除。
让我们添加另一个对象——一个名为的非持久性对象 对象 B:

这两个对象都可以从客户端状态中删除,因为它们都没有被订阅。
现在,想象一下 对象 A 有关联,并且指的是 对象 B 作为它的价值:

这种情况不会改变任何事情。 两个对象都可以从客户端状态中删除,因为它们都没有被订阅。
服务 对象 A 订阅使用 mx.data.订阅 API(注意 对象 A):

在这种情况下,两个对象都应该保持该状态。为什么?
对象 A 被订阅,因此不应被收集,因为垃圾收集机制不会删除订阅的对象。
对象 B 未被订阅,但这并不意味着可以收集,因为 对象 A 引用它。考虑一个接受 对象 A 作为参数。在这个微流中, 对象 B 检索到 检索活动 使用关联检索类型。如果垃圾回收删除 对象 B 从状态来看,该微流将无法检索它。
因此垃圾收集也应该考虑到这一点,并 对象 B 在客户端状态下。它应该这样做,因为:
- 对象 B 是“可到达的” 对象 A:它们是关联的。微流或纳流中的检索活动可以通过 对象 A.
- 对象 B 是非持久性的,这意味着它不能通过微流中的检索活动从数据库加载。如果它是持久实体(已提交),微流可以从应用程序数据库中检索它。
让我们添加一个新对象。应该发生什么 对象C 垃圾收集何时发生?

对象C 也应该保持在状态,因为可以通过 对象 A -> 对象 B -> 对象 C.
也有可能 对象 A 可以将两个不同的对象引用为同一关联的值。这怎么会发生?想象一下 对象 A 与涉及 对象 B。当你让同样的联想指向 对象D 如果不提交,则为该关联引入“更改”。在本例中, 对象 A 知道同一关联的已提交值和已更改值:

垃圾收集会考虑这种情况,并保留 对象 B 和 对象C 在该州,因为两者都可以从 对象 A.
上述所有示例都涉及非持久性对象。让我们将持久性对象添加到组合中。它们以浅蓝色背景表示:

让我们来看看当垃圾收集发生时每个可持久对象会发生什么。
对象 B 由于以下原因,不应收集:
- 此 (新) 名称末尾的后缀表示该对象尚未提交,这实际上使该对象表现得像非持久性对象。它只能在内存中找到。
- 参考自 对象 A,将其标记为“可访问”。如果它未被引用,垃圾收集可能会将其从客户端状态中删除。
对象C 由于以下原因,不应收集:
- 此
**在其名称末尾表示它有未提交的属性/关联更改。此类更改仅存在于内存中,直到 对象C 要么提交,要么回滚。 - 参考自 对象 A,将其标记为“可访问”。如果它未被引用,垃圾收集可能会将其从客户端状态中删除。
对象D 可以出于以下原因进行收集:
- 它是一个承诺的对象(没有 (新) 后缀)。
- 它也没有包含任何变化(没有
**以其名称命名)。
这意味着 对象D 不包含未提交的属性值,并且可以在需要时在应用程序的数据库中找到。因此,垃圾回收机制会将其从客户端状态中删除,即使它被引用自 对象 A.
对象E 可以被回收,因为它未被任何订阅对象引用。这意味着它根本无法访问。垃圾回收可以从客户端状态中移除此对象,包括未提交的属性/关联更改。由于该对象不再可访问,因此可以安全地移除该对象。
在上一节中我们提到 $当前会话 (系统会话)总是订阅 Mendix 客户端,但它永远不会处于该状态。现在你可以猜出原因了:可能存在引用该状态的对象 $当前会话,因此可以从中“到达”。这就是为什么它们也可能被保留在该州。
7. 结论
通过上面的描述,我们可以进一步总结垃圾收集过程如下:
垃圾收集从状态中的所有订阅对象开始,通过构建依赖关系图找到所有“可访问”对象,并通过这些对象的关联找到它们。通过分析此图,它决定是保留还是丢弃每个对象及其(如果存在)在客户端状态中的更改。
7.1 国家保管哪些物品?
- 所有订阅的对象都保持该状态。
- 如果订阅对象的所有“可访问”对象属于以下类别之一,则这些对象都会被保留:
- 非持久对象,因为它们无法在数据库中找到。
- 持久对象已创建但尚未提交,因为它们尚未位于应用程序的数据库中。
- 持久对象已提交但包含更改,因为更改尚未在数据库中 - 但微流可能需要它们。
7.2 哪些对象被从状态中移除?
- 订阅对象无法到达的对象将被丢弃。此类对象通常来自用户之前访问过的页面。
- 仍可从订阅对象访问但状态与数据库相同的对象将被丢弃。这种情况适用于可持久化对象,当它们已提交到数据库且没有任何变化时。如果需要这些对象,可以从数据库加载它们。
读完上面的描述后,你可能会感到有点紧张:

描述就到此为止。让我们练习一下你所学到的知识吧!
8.垃圾收集游戏
前面我用图文并茂的方式讲解了垃圾回收机制,现在我们来玩一个游戏,看看你是否理解了这个机制。游戏场景来自我们内部对 GC 机制的单元测试,看看你能不能猜出正确答案!
游戏很简单。下面你会看到客户端状态的表示,图例与上面相同。以下是一个例子:

通过图表,你可以看到以下几点 OBJECT 1:
- 它是一个持久实体(蓝色背景)
- 已订阅(周围有红光)
- 它尚未提交到数据库(其名称末尾有“(new)”)。
- 它包含变化。(有
**在其名称的末尾)
看到这个案例后,你可能会有一个疑问:一个“新”对象可以有变化吗?可以,因为当一个对象被创建时,它的属性会保留它们的“默认”值,这些值可以在域模型中定义。这些默认值成为属性的当前“已提交”值,对它们所做的任何更改都会存储在状态中。
对象还可以通过关联相互引用:

从上图你可以看到 OBJECT 1 与设置为的值有关联 OBJECT 2。关联的名称和基数对于此游戏都无关紧要,因此未指定。此箭头表示 OBJECT 2 可以通过 检索活动 在微流中使用 社区 检索类型。
游戏中有七个级别。每个级别代表垃圾收集发生之前“客户端状态”的当前状态。您的目标是确定在垃圾收集发生时将保留哪些状态中的对象,以及将删除哪些对象。之后,您可以单击“查看答案”以显示结果。然后,您可以继续下一关。
让游戏开始吧!
希望你喜欢这个游戏!现在你明白了如何 Mendix 客户端清理状态,这里有一些线索,可以在考虑垃圾收集的同时设计更好的应用程序。
9. 最佳实践:建模时牢记垃圾收集
9.1 不要从数据源返回太多对象
想象一下,一个列表视图带有一个微流源,该源返回数千个对象。所有这些对象都保持状态,而列表视图只能在给定时间内显示其中的一小部分,具体取决于分页配置。这种情况也适用于纳米流数据源和关联数据源。不要使用这种方法,而是尝试使用 XPath 或数据库数据源,它们针对大型数据集进行了高度优化。它只加载可以在当前页面上显示的数据。
9.2 将大实体拆分为小实体
使用大型对象会增加应用状态的大小。即使大型对象只有一次更改或只订阅了其中一个属性,整个对象也会存储在内存中。拆分此类对象可能有利于垃圾回收,尤其是对于非持久性对象。这样,如果未使用的较小对象满足上述条件,垃圾回收算法就可以删除它们。
9.3 不要创建“星形物体”
“星型对象”是指被数百个其他对象引用的对象。例如,如果你有一个 搜索结果 对象,并且有 500 搜索结果项 提到它, 搜索结果 成为明星对象。如果有订阅 搜索结果 或任何 搜索结果项 对象,所有对象可能都保持状态。如果所有对象都是非持久性的或包含更改,这种模式通常是一个问题。
如果需要使用此类对象,请确保手动提交或删除它们,或者将关联的值设置为星型对象以 空的 使用后。
9.4 在开发过程中检查状态的大小
使用 Ctrl + Alt + G 检查状态大小的快捷方式。处理大页面时,检查状态的内容尤其重要。例如,您可能会看到某个对象停留在那里,因为您忘记在自定义小部件中取消订阅它。
9.5 检查运行时日志是否存在过多状态
此 Mendix 客户端将状态的必要部分发送给运行时请求,如果从客户端发送的对象数量超过特定阈值,则会记录警告。如果您看到这样的警告,您应该重新访问该页面并调查为什么有这么多对象保留在状态中。
希望你喜欢阅读这篇文章并觉得它有用。如果你有任何评论、问题或一般赞扬 😉 请与我联系 开始.