OpenResty® 开源 Web 平台以高性能 和低内存占用著称。我们有一些用户甚至在嵌入式系统中运行复杂的 OpenResty 应用,比如机器人。也有一些用户在把他们的应用从其他技术栈(比如 Java,NodeJS 和 PHP)迁移到 OpenResty 之后,观察到内存使用量上的显著下降。
然而,有时候我们还是需要优化某些 OpenResty 应用的内存使用。这些应用中的 Lua 代码、NGINX 配置、第三方 Lua 库或第三方 NGINX 模块都可能会有 BUG 或者性能问题,从而导致应用占用过多的内存,甚至存在内存泄露。
为了有效地调试和优化内存的过度使用或者内存泄漏问题,我们需要了解 OpenResty、Nginx 和 LuaJIT 在内部是如何分配和管理内存的。
我们的 OpenResty XRay 商业产品,能够在不修改目标应用的情况下,自动分析和诊断几乎所有的内存使用问题,即使是线上的生产应用。
我们将撰写一个系列的文章(本文是第一篇),使用 OpenResty XRay 在真实案例里获取到的数据和图表,来详细阐述 OpenResty、Nginx 和 LuaJIT 的内存分配和管理机制。
下面我们首先介绍 Nginx 进程在系统层面的内存占用分布,然后再逐个介绍应用层面的各种内存分配器。
系统层面
在现代操作系统中,进程在最高层面上申请和使用的内存都是虚拟内存。操作系统为每个进程分配和管理虚拟内存,并将实际使用的虚拟内存页,映射到物理内存页上去(比如 DDR4 内存条等设备里的)。
一个很重要的概念是,进程可能会申请很多的虚拟内存空间,而实际只使用其中很小一部分。比如,一个进程可以向操作系统申请 2TB 的虚拟内存空间,即使当前系统只有 8GB 的物理内存(RAM)。只要这个进程没有在这个巨大的虚拟内存空间中读写很多内存页,就不会有任何问题。
这部分实际映射到物理内存设备上的虚拟内存空间,才是我们真正需要关注的。所以不要因为看到 ps
或者 top
里显示占用了很大的虚拟内存空间(通常叫做 VIRT
)而感到惊慌。
实际使用的那一小部分虚拟内存(即读写了数据的),通常被叫做 RSS
,即 常驻内存
(resident memory)。当系统的物理内存快耗尽的时候,一部分常驻内存页里的数据会被 交换 到硬盘上^1。这部分被交换出去的内存空间不再是常驻内存的一部分,而是成为 “交换出去的内存”(简称 "swap")。
[^1]: 现代安卓(Android)操作系统支持将内存页交换到内存,但那些内存页是经过压缩的ÿ