现代语言中的内存管理通常是事后才考虑的。在很大程度上,我们编写软件时几乎不考虑内存。这对我们很有帮助,但总有例外……
在加州,地方教育机构(LEA)有广泛的财务报告要求,LEA 可以是县、学区、特许学校或单所学校。大多数 LEA 创建自己的财务报告,这些报告通常以 Excel 为中心,每份报告都不同也就不足为奇了。为了解决这个问题,加州教育委员会委托开发软件来生成财务报告。
我是开发团队的一员。
我首先查看了测试日志,Ed-Pro 的日志指向高内存使用率,也许存在内存泄漏?一位工程师观察到 Ed-Pro 的计算使用了大量短生命周期的内存。如果内存没有被快速清理,它可能看起来像内存泄漏。
Ed-Pro 构建在 .Net Core 之上,这是微软的多平台框架。在 .Net Core 中,内存分为三个代:短生命周期(Gen0)、中等生命周期(Gen1)和长生命周期(Gen2)。Gen0 用于快速超出作用域的短生命周期数据,Gen1 用于停留时间稍长的中等生命周期内存,它最终也会超出作用域,Gen2 是长生命周期内存,可能在应用程序的整个生命周期内存在。Gen0 内存不断被回收,Gen1 的回收频率低于 Gen0,Gen2 的回收频率甚至低于 Gen1。
理解 Ed-Pro 内存使用情况的唯一确定方法是对其进行分析,下面是使用 JetBrains 的 dotMemory 的屏幕截图。
正如预期的那样,我们发现了大量的 Gen0 内存(蓝色部分),以至于垃圾回收似乎跟不上。一种补偿大量内存的策略导致垃圾回收在增加内存空间(为应用程序的使用添加更多内存)和清理之间振荡。在清理周期中,应用程序无响应。
起初,我们感到困惑,垃圾回收的目的不就是保持内存整洁吗?两篇文章对我们理解 .Net 中垃圾回收的工作原理起到了重要作用:Mark Vincze 的文章 Troubleshooting high memory usage with ASP.Net Core on Kubernetes 和 Microsoft 的 Fundamentals of Garbage Collection。两者都是很好的阅读材料,为 Ed-Pro 中的内存使用情况带来了清晰的认识。
以下是我们所学内容的总结,.Net 中有两种类型的垃圾回收:服务器垃圾回收和工作站垃圾回收。
服务器垃圾回收做了几个假设:首先,有充足的内存可用,其次,处理器是多核的且速度很快。两者都可能是真的,但我们生活在虚拟机和 Docker 的世界中,这两个假设都是假的可能性更大。
服务器垃圾回收允许内存积累,在某个时刻,它会做两件事之一:要么增加内存空间允许内存增长,要么释放孤立的内存。当它选择释放内存时,垃圾回收在高优先级线程上启动该过程。高优先级线程的优先级高于应用程序;如果机器速度快,清理应该不会被注意到。但是,如果不是,它会导致应用程序暂停,直到清理完成。
工作站垃圾回收的操作方式不同。它在与应用程序优先级相同的线程上连续运行以回收内存。这意味着它也在与应用程序竞争资源,这可能导致应用程序变慢。好处是应用程序的内存使用可以保持相当低,特别是当它使用大量 Gen0 时。
默认情况下,如果 .Net Core 检测到服务器,它会运行服务器垃圾回收类型,这正是我们应用程序的情况。要运行工作站垃圾回收类型,请将以下代码片段添加到您的项目文件中:
<PropertyGroup>
<ServerGarbageCollection>false</ServerGarbageCollection>
</PropertyGroup>
我们对 Ed-Pro 进行了此配置更改,使用 dotMemory,我们在启用工作站垃圾回收的情况下对 Ed-Pro 的内存进行了分析,并加载了与之前测试相同的屏幕。以下是结果:
内存使用量显著降低。Gen0 分配几乎不存在。除了图表中的差异外,服务器垃圾回收内存使用量达到 1 GB,而工作站垃圾回收达到大约 200 MB。
每个应用程序都是不同的。我们的应用程序使用了大量临时数据,因此使用了大量 Gen0 内存。您的应用程序可能利用更长生命周期的内存,如 Gen1 或 Gen2,在这种情况下服务器垃圾回收非常有意义。我的建议是在不同条件下对您的内存进行分析,以了解内存的使用方式,然后决定哪种模式最适合您的应用程序。
作者:Chuck Conway 是一位 AI 工程师,拥有近 30 年的软件工程经验。他构建实用的 AI 系统——内容管道、基础设施代理和解决实际问题的工具——并分享他沿途的学习成果。在社交媒体上与他联系:X (@chuckconway) 或访问他的 YouTube 和 SubStack。