1

我们在使用 Akka HTTP 构建的 Web 服务器上遇到了奇怪的内存行为。我们的架构是这样的:

  1. Web 服务器路由调用各种参与者,获取未来的结果并将其流式传输到响应
  2. 参与者调用非阻塞操作(使用期货),组合和处理从它们获取的数据并将结果传递给发送者。我们使用标准的 Akka 演员,实现其接收方法(不是 Akka 类型)
  3. 应用程序中的任何地方都没有阻止代码

当我在本地运行 Web 服务器时,一开始大约需要 350 MB。第一次请求后,内存使用量跃升至 430 MB 左右,并随着每个请求缓慢增加(使用 Mac 上的 Activity Monitor 进行监控)。但是GC不应该在每次请求后清理东西吗?处理后的内存使用量不应该再次为 350 MB 吗?

我还安装了YourKit java分析器,这是一个头记忆图

.

可见,一旦内存使用量增加,就再也回不去了,系统是无状态的。此外,当我从分析器手动运行 GC 时,它几乎什么都不做,只是内存使用量略有减少。我知道有些服务可能会在第一次请求后缓存东西,暂时消耗内存,但是 Akka Actors 或 Akka HTTP 内部有没有关于这个的策略?

我试图检查离 GC 最远的对象,但它只显示库类和内置类的 Akka,与我们的代码无关。

所以,我有两个问题:

  1. 消息处理后actor如何关闭资源并释放内存?你有过类似的经历吗?
  2. 有没有更好的方法来分析 Akka HTTP,它会告诉我使用离 GC 最远的分类的堆栈跟踪?

附带说明一下,是否建议在 Actors 内使用调度程序(在 Akka HTTP 服务器内运行)?当我这样做时,似乎内存使用量大幅增加,并且应用程序在 DEV 环境中运行我们的内存。

提前致谢,

阿米尔

4

1 回答 1

0

一个actor保持活动状态直到它被显式停止:没有垃圾收集。

管理 Actor 生命周期的两种最常用的方法(除了 Actor 本身决定是时候停止之外)可能是:

  • 家长有责任阻止孩子。例如,如果演员是为了代表父母执行特定任务而产生的,则需要这种方法。

  • 使用不活动超时。如果参与者代表域实体(例如,每个用户帐户的参与者,该参与者在某种意义上用作内存缓存),则使用context.setReceiveTimeout导致ReceiveTimeout在超时后向参与者发送消息(请注意,在在某些情况下,如果消息在邮箱中排队但在超时到期时未处理,则该消息的预定发送可能不会及时取消:接收 aReceiveTimeout并不能保证自上次收到消息以来超时已经过去)是一个漂亮的合理的解决方案,特别是如果使用 Akka Persistence 和 Akka Cluster Sharding 来恢复 actor 的状态。

更新添加:

关于

GC 不应该在每次请求后清理东西吗?

简短的回答是否定的,GC 不会在每次请求后清理东西(如果确实如此,这是一个很好的迹象,表明您没有提供足够的内存)。

更长的答案是 JVM 上垃圾收集的特性非常不明确:垃圾收集实现必须遵守的唯一规则是它永远不会释放可从 GC 根访问的对象(基本上是线程堆栈上的任何变量或静态到一个类)通过一个强引用链。垃圾收集器何时甚至是否回收垃圾占用的空间完全取决于实现(我说“是否”是为了说明 Epsilon 垃圾收集器的存在,它从不释放内存;这对于在没有复杂性的情况下对 JVM 进行基准测试很有用垃圾收集以及在应用程序内存不足时可以重新启动的环境中:JVM 崩溃在某种意义上是实际的垃圾收集器)。

您可以尝试java.lang.System.gc在服务器停止时执行:这可能会导致 GC 运行(请注意,在这种情况下不需要系统实际收集任何垃圾)。如果垃圾收集器将释放任何内存,那么它必须运行的唯一时间是没有足够的空间来满足对象分配请求:因此,如果应用程序停止分配对象,则可能不会运行垃圾收集。

出于性能原因,JVM 中的大多数现代垃圾收集器会等到空闲空间不超过一定数量时才会收集垃圾:这是因为回收所有空间所花费的时间与不可回收的对象数量成正比对于很多应用程序来说,模式是大多数对象都是相对短暂的,因此不可回收的对象的数量是相当恒定的。这样做的结果是垃圾收集器将在给定应用程序的“完整”GC 中完成大约相同数量的工作,而不管有多少可用空间。

于 2020-07-16T14:06:14.753 回答