跳转至

LuatOS 内存(RAM)使用分析

作者:孟伟 | 最后修改:2026-04-16

一、引言

内存管理是嵌入式开发中的核心课题,尤其在资源受限的物联网设备中显得尤为重要。LuatOS 作为一款专为嵌入式设备设计的操作系统,其内存管理机制直接关系到固件的稳定性与性能表现。本文将从内存基础知识、LuatOS 内核内存分配机制、常见内存问题场景以及内存问题分析方法四个维度,系统性地讲解 LuatOS 的内存管理机制,帮助开发者更好地理解和使用 LuatOS 进行二次开发。

二、内存基础知识

2.1 随机存取存储器(RAM)概述

随机存取存储器(Random Access Memory,简称 RAM)是计算机系统中用于临时存储数据和程序指令的硬件设备。与只读存储器(ROM)不同,RAM 是一种易失性存储器,这意味着当设备断电时,存储在 RAM 中的数据会全部丢失。在嵌入式系统中,RAM 的容量通常非常有限,从几十 KB 到几 MB 不等,这要求开发者在编写代码时必须精打细算,合理规划内存使用。

RAM 的主要作用是为正在运行的程序提供快速的数据访问通道。当我们启动一个程序时,操作系统会将程序代码和数据加载到 RAM 中,CPU 通过直接访问 RAM 来执行指令和处理数据。在 LuatOS 环境中,Lua 虚拟机、用户脚本、运行时数据等都需要占用 RAM 空间。由于 RAM 资源有限且宝贵,开发者需要深入理解内存分配机制,避免内存泄漏和浪费。

从物理层面来看,RAM 由大量的存储单元组成,每个单元可以存储 1 位(bit)的数据。这些存储单元按照行和列的方式组织成二维阵列,通过行地址选通线(Row Address Strobe)和列地址选通线(Column Address Strobe)来定位和访问特定的存储单元。现代 RAM 芯片通常采用同步动态随机存取(SDRAM)技术,能够与系统时钟同步工作,提供更高的数据传输效率。

2.2 Lua 虚拟机内存模型

LuatOS 基于 Lua 脚本语言开发,因此理解 Lua 虚拟机的内存模型对于内存管理至关重要。Lua 虚拟机采用自动内存管理机制,开发者无需手动分配和释放内存,这一特性大大简化了开发流程,但也带来了内存管理的隐性问题。Lua 虚拟机维护着一个内存池,所有 Lua 对象(包括字符串、表、函数、用户数据等)都从这个内存池中分配。

Lua 虚拟机将内存分为三个主要部分: 已分配内存 (allocated memory)、 已使用内存 (used memory)和 碎片内存 (fragmented memory)。已分配内存是 Lua 虚拟机从系统中获取的总内存大小;已使用内存是当前正在被 Lua 对象使用的内存;碎片内存则是已被释放但尚未被重新利用的内存碎片。这三者的关系直接影响了内存的使用效率和可用性。

Lua 的字符串实现采用了独特的 内部化(interning)机制 和不可变性(immutability)策略。每个字符串在内存中都是唯一的,当程序创建相同内容的字符串时,Lua 会复用已有的字符串对象,而不是创建新的副本。这种设计可以有效减少重复字符串的内存占用。然而,字符串拼接操作的实现方式导致了内存效率问题:当拼接两个字符串时,Lua 会分配一块新的内存来存储结果,然后将原字符串的内容复制到新内存中,最后更新引用指针。这意味着每次拼接操作都会产生临时内存分配和复制开销。

Lua 的表(table)是另一种重要的数据类型,既可以作为数组使用,也可以作为字典使用。表在内存中由两个部分组成: 数组部分(array part) 和 哈希部分(hash part) 。数组部分用于存储整数索引的元素,哈希部分用于存储字符串索引或其他非整数键的元素。Lua 虚拟机会根据表的使用情况动态调整这两个部分的大小,但这种调整本身也会产生内存开销。当向表中插入大量元素时,表的内存占用可能会显著增加,尤其是在频繁插入和删除操作的场景下。

三、LuatOS 内存分配机制

3.1 模组内存总体规划

在不同的模组中 ram 也不一样,具体的分配情况也不相同。

以 780ehm 模组为例,该模组具有 8MB RAM 的模组,根据 780ehm 模组的实际测试结果:

I/user.mem.lua 4194296 35752 35752  -- Lua 内存:4MB 总,35KB 已用,35KB 峰值
I/user.mem.sys 3211584 102048 112264  -- 系统内存:约 3.06MB 总,102KB 已用,112KB 峰值

其内存分配大致如下:

内存区域
大小
占比
主要用途
Lua 虚拟机内存
4MB
50%
Lua 脚本运行、变量存储、GC 堆
系统内存
3MB
37.5%
内核堆、任务栈、中断栈、驱动缓冲区
保留/隐藏区域
1MB

12.5%
系统固件保留,专门用于保障核心系统功能的稳定运行与硬件性能

这约 1MB 的"隐藏"内存主要分配给了通信协议栈缓存、音频处理缓冲区、WiFi 缓冲区(如适用)、安全引擎缓存和 DMA 描述符等系统组件。这种分配方式体现了 LuatOS 的设计理念:优先保障系统核心功能的稳定运行,然后将剩余资源分配给 Lua 虚拟机。

780EHM 模组的 PSRAM 说明

对于 780EHM 模组来说:

  1. 物理内存构成:780EHM 使用的是 EC718HM 系列芯片,该系列芯片配备了物理 PSRAM
  2. 内存区域映射:

sys 内存:实际在 PSRAM 上,与 psram 是同一个东西,数据完全一样

lua 内存:实际在 PSRAM 上,独占一块内存 3. 内存分配关系:

8MB 内存全部位于物理 PSRAM 上

从用户视角看到的 sys/lua/保留区域,只是逻辑上的划分,物理上都在 PSRAM 中

psram 内存区域与 sys 内存区域在 780EHM 上是完全相同的,只是不同的命名方式

不同模组的内存分配差异

需要注意的是,在不同的 BSP 下,LuatOS 固件的内存分配存在巨大差异,而且大小与具体的固件编译配置有关,使用情况又跟具体的库的实现有关系。

3.2 内存查询接口详解

在 LuatOS 中,rtos.meminfo() 是最核心的内存查询接口,用于获取不同类型内存的使用情况:

rtos.meminfo(type)

功能

获取 LuatOS 内存信息。不同芯片平台、固件编译配置下的内存分配和使用方式存在显著差异。

内存区域与物理内存关系

首先需要理解物理内存和逻辑内存的区分:

  1. 物理内存类型(用大写的 SRAM/PSRAM 代表物理内存):

  2. SRAM:静态随机存取存储器,访问速度快但容量较小;一定存在, 但不一定会暴露给客户使用

  3. PSRAM:外接伪静态随机存取存储器,访问速度较慢但容量较大;不一定存在, 也不一定会启用给客户使用
  4. 注意 区分 SRAM 和 PSRAM 的差异, 单从硬件上说, SRAM 更快但小, PSRAM 慢但更大.
  5. 逻辑内存类型(用户可见), sys/psram/lua 是 luatos 暴露给用户展示的内存分配布局:

  6. 虚拟机内存("lua"):Lua 虚拟机使用的内存区域,包括 Lua 脚本中的变量、函数、表、字符串等

  7. 系统内存("sys"):系统级内存区域,用于 FreeRTOS 任务栈、驱动缓冲区等
  8. psram 内存("psram"):专门管理的 PSRAM 区域,用于大容量数据缓存

注意:逻辑内存与物理内存的对应关系因平台而异!

不同平台的差异:

平台系列
lua 区域
sys 区域
psram 区域
说明
Air780EPM/EHM/Air8000
在PSRAM上

在PSRAM上
在PSRAM上
sys和psram指向同一PSRAM,只是统计角度不同
Air780E/EG
静态分配(通常SRAM)
在SRAM上
不存在
不支持PSRAM,lua内存固定大小

Air780EP/EPV
可能SRAM或PSRAM
在SRAM上
可选(需开启PSRAM支持)
云编译固件可启用PSRAM

Air8101
在PSRAM上
在SRAM上
在PSRAM上
lua从psram分配但不计入统计
PC模拟器
独立内存
模拟SRAM
模拟PSRAM
仅用于开发调试

各区域主要功能

  1. 虚拟机内存("lua"):

  2. Lua 脚本执行环境

  3. Lua 变量、函数、表、字符串等动态分配
  4. 由 Lua 垃圾回收器管理
  5. 开发关注重点:避免内存泄漏,控制内存使用峰值
  6. 系统内存("sys"):

  7. FreeRTOS 内核堆(Heap)

  8. 任务栈、中断栈
  9. 驱动缓冲区(GPIO、UART、I2C 等)
  10. 系统级任务:任务调度、中断处理、消息队列等
  11. 用户涉及有限:主要通过 zbuff 接口间接使用
  12. PSRAM 内存("psram"):

  13. 大容量数据缓存(图像/音频处理)

  14. 网络通信大文件接收(TCP/UDP/OTA)
  15. zbuff 缓冲区分配
  16. 注意:并非所有平台都支持 PSRAM

参数

type

参数含义:"sys"系统内存, "lua"虚拟机内存,"psram"psram内存, 默认为lua虚拟机内存;
数据类型:string;
取值范围:无特别限制;
是否必选:可选传入此参数;
注意事项:暂无;
参数示例:sys;

返回值

local total_lua, used_lua, max_used_lua = rtos.meminfo("lua")

total_lua

含义说明:总内存大小,单位字节;
数据类型:number;
取值范围:暂无;
注意事项:暂无;
返回示例:4194296;

used_lua

含义说明:当前已使用的内存大小,单位字节;
数据类型:number;
取值范围:暂无;
注意事项:暂无;
返回示例:16424;

max_used_lua

含义说明:历史最高已使用的内存大小,单位字节;
数据类型:number;
取值范围:暂无;
注意事项:暂无;
返回示例:16424;

使用示例:

-- rtos.meminfo() 接口使用示例

-- 1. 查询 Lua 虚拟机内存(默认)
local total_lua, used_lua, max_used_lua = rtos.meminfo("lua")
log.info("Lua Memory:",
    "Total:", total_lua / 1024, "KB",
    "Used:", used_lua / 1024, "KB",
    "Peak:", max_used_lua / 1024, "KB")

-- 2. 查询系统内存
local total_sys, used_sys, max_used_sys = rtos.meminfo("sys")
log.info("System Memory:",
    "Total:", total_sys / 1024, "KB",
    "Used:", used_sys / 1024, "KB",
    "Peak:", max_used_sys / 1024, "KB")

-- 3. 查询 PSRAM 内存(如果支持)
local total_psram, used_psram, max_used_psram = rtos.meminfo("psram")
if total_psram and total_psram > 0 then
    log.info("PSRAM Memory:",
        "Total:", total_psram / 1024, "KB",
        "Used:", used_psram / 1024, "KB",
        "Peak:", max_used_psram / 1024, "KB")
end

四 Lua 垃圾回收深度解析

本章节介绍是 lua 虚拟机内存回收原理。

4.1 垃圾回收工作原理

Lua 采用 增量标记-扫描(Incremental Mark-and-Sweep) 算法进行垃圾回收,这是一种高效的自动内存管理机制。该算法的工作过程分为两个主要阶段:

  1. 标记阶段(Mark Phase):从根对象(全局变量、活跃的局部变量、寄存器等)开始,遍历所有可达对象,将其标记为"存活"状态。
  2. 扫描阶段(Sweep Phase):遍历所有对象,回收未被标记的对象,将其占用的内存标记为可用。

Lua 的增量垃圾回收将标记和扫描过程分解为多个小步骤,与程序执行交替进行。这种设计可以减少垃圾回收对程序响应时间的影响,特别适合实时性要求较高的嵌入式系统。

4.1.1 垃圾回收示例与图解

下面通过一个具体的 Lua 脚本示例,结合标记图解来详细讲解垃圾回收的过程:

-- Lua 垃圾回收示例
log.info("初始内存使用:", collectgarbage("count"), "KB")

-- 创建一些对象
local function create_objects()
    -- 创建根对象:全局变量(可达)
    _G.global_table = {}

    -- 创建局部变量:活跃的局部变量(可达)
    local local_table = {}

    -- 创建嵌套对象
    _G.global_table.nested = {}
    local_table.nested = {}
    -- 创建循环引用对象(可达)
    local cycle1 = {}
    local cycle2 = {}
    cycle1.ref = cycle2
    cycle2.ref = cycle1
    -- 创建不可达对象链
    local unreachable = {}
    unreachable.next = {}
    unreachable.next.next = {}
    log.info("创建对象后内存使用:", collectgarbage("count"), "KB")
    -- 返回局部变量,使其成为根对象
    return local_table, cycle1
end
-- 执行对象创建
local active_table, active_cycle = create_objects()
-- 现在:
- _G.global_table :全局变量,直接被_G引用,是根对象,处于存活状态。
-- local_table (返回后为active_table):局部变量,通过返回值被外部引用,是根对象,处于存活状态。
-- _G.global_table.nested :嵌套对象,被_G.global_table引用,可达,处于存活状态。
-- local_table.nested (返回后为active_table.nested):嵌套对象,被local_table引用,可达,处于存活状态。
-- cycle1 (返回后为active_cycle):局部变量,通过返回值被外部引用,与cycle2循环引用,是根对象,处于存活状态。
-- cycle2 :局部变量,与cycle1循环引用,通过active_cycle可达,处于存活状态。
-- unreachable :局部变量,函数返回后无外部引用,不可达,成为垃圾。
-- unreachable.next :嵌套对象,被unreachable引用,不可达,成为垃圾。
-- unreachable.next.next :嵌套对象,被unreachable.next引用,不可达,成为垃圾。
log.info("执行后内存使用:", collectgarbage("count"), "KB")
-- 手动触发垃圾回收
collectgarbage("collect")
log.info("垃圾回收后内存使用:", collectgarbage("count"), "KB")

4.1.2 垃圾回收标记图解

逐行执行状态:

**create_objects函数执行**

**_G.global_table = {}**
--创建全局变量,根对象,存活

**local local_table = {}**
--创建局部变量,存活

**第3-4行:创建嵌套对象**
-- _G.global_table.nested:存活
-- local_table.nested:存活

**第5-8行:创建循环引用对象**
--cycle1、cycle2:存活,相互循环引用

**第9-11行:创建不可达对象链**
-- unreachable:存活
-- unreachable.next:存活
-- unreachable.next.next:存活,形成链表

**第12行:内存记录**
-- 打印创建对象后内存使用

**第13行:return local_table, cycle1**
-- local_table → active_table:外部变量,根对象,存活
-- cycle1 → active_cycle:外部变量,根对象,存活
-- unreachable:及其链表:无外部引用,不可达,成为垃圾

**函数执行后内存记录**
log.info("执行后内存使用:", collectgarbage("count"), "KB")
--打印函数执行后内存使用

**手动触发垃圾回收**
collectgarbage("collect")
-- 标记阶段:标记根对象及其可达对象
-- 扫描阶段:回收unreachable及其链表

**垃圾回收后内存记录**
log.info("垃圾回收后内存使用:", collectgarbage("count"), "KB")
-- 打印垃圾回收后内存使用,内存降低

初始状态:

根对象集:
├── _G.global_table (全局变量)
├── active_table (局部变量)
└── active_cycle (局部变量)

对象图:
_G.global_table → nested
active_table → nested
active_cycle ↔ cycle2 (循环引用)
unreachable → next → next → nil (不可达)

标记阶段(Mark Phase):

  1. 步骤 1:标记根对象
_G.global_table (标记为存活)
active_table (标记为存活)
active_cycle (标记为存活)
unreachable (未标记)
  1. 步骤 2:遍历标记可达对象
_G.global_table → nested (标记为存活)
active_table → nested (标记为存活)
active_cycle ↔ cycle2 (标记为存活)
unreachable → next → next → nil (未标记)

扫描阶段(Sweep Phase):

  • 遍历所有对象,回收未标记的对象
_G.global_table → nested (保留)
active_table → nested (保留)
active_cycle ↔ cycle2 (保留)
unreachable → next → next → nil (回收)

4.1.3 增量垃圾回收过程

增量垃圾回收将上述过程分解为多个小步骤,与程序执行交替进行:

  1. 程序执行:创建对象、修改引用关系
  2. 垃圾回收步骤 1:标记一部分根对象
  3. 程序继续执行
  4. 垃圾回收步骤 2:遍历标记一部分可达对象
  5. 程序继续执行
  6. 垃圾回收步骤 3:继续标记剩余可达对象
  7. 程序继续执行
  8. 垃圾回收步骤 4:扫描一部分内存,回收未标记对象
  9. 程序继续执行
  10. 垃圾回收步骤 5:完成剩余内存的扫描和回收

这种交替执行的方式避免了长时间暂停程序执行,适合实时性要求较高的嵌入式系统。

4.2 垃圾回收控制参数

Lua 垃圾回收器的行为由两个关键参数控制:

  1. 间歇率(Pause):控制垃圾回收器开始新一轮回收的阈值。默认值为 200,表示当内存使用量达到上次回收时的两倍时,开始新的回收循环。

  2. 值越小,垃圾回收越频繁,内存占用越低,但 CPU 开销越大

  3. 值越大,垃圾回收间隔越长,内存占用越高,但 CPU 开销越小
  4. 步进倍率(Step Multiplier):控制垃圾回收器的工作速度相对于内存分配速度的倍率。默认值为 200,表示垃圾回收器以内存分配速度的两倍工作。

  5. 值小于 100 时,垃圾回收速度慢于内存分配速度,可能导致内存无限增长

  6. 值越大,垃圾回收越积极,每次回收的内存越多

4.3 collectgarbage() 函数详解

Lua 提供了 collectgarbage([opt [, arg]]) 函数用于控制自动内存管理,该函数在 LuatOS 中同样可用。以下是其主要用法:

选项
功能
参数
返回值
"collect"
执行一次完整的垃圾回收循环
无

无
"count"
返回 Lua 使用的总内存(KB)
无
内存大小(带小数)
"restart"
重启垃圾回收器的自动运行
无
无
"setpause"
设置垃圾回收器的间歇率
新的间歇率值
之前的间歇率
"setstepmul"
设置垃圾回收器的步进倍率
新的步进倍率值
之前的步进倍率
"step"
单步运行垃圾回收器
步长大小(0 表示最小步长)
布尔值(是否结束循环)

"stop"
停止垃圾回收器的自动运行
无
无

4.4 collectgarbage() 核心参数详解

4.4.1 重点参数使用示例

下面通过一个详细的示例,结合日志输出,重点讲解 collectgarbage() 函数的三个核心参数:"collect"、"count" 和 "setpause"。

-- collectgarbage() 核心参数示例

-- 辅助函数:格式化内存输出
local function format_mem(mem)
    return string.format("%.2f KB", mem)
end
-- 1. 初始状态:使用 count 参数监控内存
log.info("=== 1. 初始内存状态 ===")
local initial_mem = collectgarbage("count")
log.info("初始内存:", format_mem(initial_mem))
-- 2. 创建大量对象
log.info("=== 2. 创建大量对象 ===")
local function create_objects(count)
    local objects = {}
    for i = 1, count do
        objects[i] = {
            id = i,
            name = string.rep("object_" .. i, 10),
            data = {}
        }
    end
    return objects
end

local all_objects = create_objects(5000)
local after_create_mem = collectgarbage("count")
log.info("创建 5000 个对象后内存:", format_mem(after_create_mem))
log.info("内存增加:", format_mem(after_create_mem - initial_mem))

-- 3. 释放部分对象引用
log.info("=== 3. 释放部分对象引用 ===")
for i = 3001, 5000 do
    all_objects[i] = nil
end
local after_release_mem = collectgarbage("count")
log.info("释放 2000 个对象后内存:", format_mem(after_release_mem))
log.info("内存变化:", format_mem(after_release_mem - after_create_mem))

-- 4. 使用 collect 参数手动触发垃圾回收
log.info("=== 4. 使用 collect 参数触发垃圾回收 ===")

--下面手动释放内存
--释放引用≠释放内存 :将对象引用设为 nil 只是断开了引用关系,对象占用的内存不会立即释放
--垃圾回收时机 :Lua默认会自动运行垃圾回收,但可以通过 collectgarbage("collect") 手动触发
--手动GC的意义 :在内存敏感操作前或批量释放对象后手动触发GC,可以立即释放内存,避免内存占用过高
collectgarbage("collect")  -- 执行完整的垃圾回收循环
local after_collect_mem = collectgarbage("count")
log.info("手动 GC 后内存:", format_mem(after_collect_mem))
log.info("GC 释放内存:", format_mem(after_release_mem - after_collect_mem))


-- 5. 综合示例:监控内存并手动 GC
log.info("\n=== 6. 综合示例:循环创建和回收 ===")
local max_iterations = 5

for i = 1, max_iterations do
    -- 创建对象_
    local temp = create_objects(2000)

    -- 释放引用
    temp = nil

    -- 监控内存
    local mem_before = collectgarbage("count")

   -- 手动 GC
    collectgarbage("collect")

   local mem_after = collectgarbage("count")
    log.info("第", i, "次循环 - GC 前:", format_mem(mem_before), "GC 后:", format_mem(mem_after), "释放:", format_mem(mem_before - mem_after))
end

4.4.2 核心参数详解

"count" 参数

功能:返回 Lua 虚拟机当前使用的总内存,单位为 KB(带小数精度)。

使用场景:

  • 监控内存使用趋势
  • 调试内存泄漏问题
  • 评估垃圾回收效果

日志解析:

=== 1. 初始内存状态 ===
初始内存: 120.50 KB
=== 2. 创建大量对象 ===
创建 5000 个对象后内存: 356.75 KB
内存增加: 236.25 KB

关键说明:

  • 返回值包含小数部分,提供精确的内存使用情况
  • 包括 Lua VM 管理的所有内存,包括已分配但未使用的内存
  • 是监控内存变化的主要工具
"collect" 参数

功能:执行一次完整的垃圾回收循环,包括标记、扫描和清理阶段。

使用场景:

  • 手动触发垃圾回收,释放不再使用的内存
  • 在内存敏感操作前清理内存
  • 测试和调试垃圾回收行为

日志解析:

=== 4. 使用 collect 参数触发垃圾回收 ===
手动 GC 前内存: 356.75 KB
手动 GC 后内存: 220.30 KB
GC 释放内存: 136.45 KB

关键说明:

  • 执行完整的垃圾回收流程,释放所有不可达对象
  • 阻塞执行,直到垃圾回收完成
  • 适用于需要立即释放内存的场景
"setpause" 参数

功能:设置垃圾回收器的间歇率,控制垃圾回收开始的阈值。

工作原理:

  • 间歇率默认值为 200,表示当总内存使用量达到上次 GC 后活跃内存的 2 倍时,触发新一轮 GC
  • 公式:触发条件 = 当前内存 > (基准内存 × pause / 100)

  • 基准内存:每次 GC 完成后更新的活跃内存量

  • pause 值:百分比单位,100 表示 100%

使用场景:

  • 调整垃圾回收频率,平衡内存使用和 CPU 开销
  • 针对不同应用场景优化垃圾回收行为
  • 在内存紧张时降低间歇率,提高回收频率

关键说明:

  • 间歇率越低,垃圾回收越频繁,内存占用越低,但 CPU 开销越大
  • 间歇率越高,垃圾回收间隔越长,内存占用越高,但 CPU 开销越小
  • 不同应用场景需要不同的设置:

  • 内存敏感应用:设置较低的间歇率(如 100-150)

  • CPU 敏感应用:设置较高的间歇率(如 300-500)
  • 平衡需求:使用默认值 200

4.4.3 最佳实践

  1. 内存监控:
sys.timerLoopStart(function()
    log.info("mem.lua", rtos.meminfo())
    log.info("mem.sys", rtos.meminfo("sys"))
    --log.info("mem.psram", rtos.meminfo("psram"))  --需要时打开
end, 3000)
  1. 手动 GC 时机:
-- 在内存敏感操作前执行手动 GC
collectgarbage("collect")  -- 清理内存
  1. 间歇率优化:
-- 根据应用场景调整间歇率
collectgarbage("setpause", 150)  -- 更频繁的 GC
--collectgarbage("setpause", 300)  -- 较少的 GC,节省 CPU

通过合理使用这三个核心参数,可以有效监控和控制 Lua 虚拟机的内存使用,优化应用程序的性能和稳定性。

五、实际功能内存使用案例分析

为了更好地理解 LuatOS 中三种内存的使用情况,下面结合具体的脚本代码,用 780EHM 来详细分析不同功能在运行过程中如何使用 lua、sys 和 psram 内存。

对于 780EHM 来说,sys 分区和 psram 分区都在 PSRAM 上,实际是同一个东西, 数据完全一样,所以只看 sys 内存即可。

5.1 zbuff 功能内存使用分析

zbuff 是 LuatOS 中用于直接操作二进制内存数据的库,内存组成:lua 对象元数据(小) + C 层数据块(大,如存在 PSRAM 则在 psram 中进行申请,如不存在或失败则在 SRAM 上的 sys 中进行申请)。

-- zbuff 内存使用示例_
log.info("初始内存状态:")
log.info("lua:", rtos.meminfo("lua"))
--对于780EHM来说,sys分区和psram分区都在PSRAM上,实际是同一个东西, 数据完全一样,所以只看sys内存即可。
--对于PSRAM以及SRAM都存在的模组,如存在PSRAM则在psram中进行申请,如不存在或失败则在SRAM上的sys中进行申请,
--所以需要根据具体模组来打印sys分区以及psram分区_
log.info("sys:", rtos.meminfo("sys"))
log.info("psram:", rtos.meminfo("psram"))

-- 1. buff_auto这个Lua对象是从lua内存区域分配的,这个对象指向的1MB字节的内存是从sys内存区域分配的
local buff_auto = zbuff.create(1024 * 1024)  -- 申请 1MB 内存
local buff_with_data = zbuff.create(512 * 1024, "initial data")  -- 申请 512KB 带初始数据的 zbuff
log.info("创建 zbuff 后:")
log.info("lua:", rtos.meminfo("lua"))  
log.info("sys:", rtos.meminfo("sys"))  

-- 2. 使用 zbuff 读写数据
-- 数据读写操作主要影响实际数据块所在的内存
-- 这里只修改数据内容,不改变内存分配情况
buff_auto:write("Hello zbuff!")
buff_with_data:write("Hello zbuff with initial data!")
log.info("写入数据后内存状态:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))


-- 3. 释放 zbuff 内存
-- 调用 free() 立即释放实际数据块
-- Lua 对象元数据仍需等待 GC 回收
buff_auto:free()
buff_with_data:free()
log.info("调用 free() 后:")
log.info("lua:", rtos.meminfo("lua"))  
log.info("sys:", rtos.meminfo("sys")) 


-- 4. 释放 Lua 对象引用
-- 将引用设为 nil,等待 GC 回收 zbuff 对象元数据
buff_auto = nil
buff_with_data = nil
collectgarbage("collect")  -- 手动触发 GC
log.info("释放引用并 GC 后:")
log.info("lua:", rtos.meminfo("lua"))  
log.info("sys:", rtos.meminfo("sys"))  

内存使用分析:

步骤 1:初始内存状态

  • lua 内存:4194296 34688 34912(总大小 4MB,已用 34688B,可用 34912B)
  • sys 内存:3211584 104760 105880(总大小 3.2MB,已用 104760B,可用 105880B)

步骤 2:创建 zbuff 后

  • 代码操作:创建 1MB 和 512KB 的 zbuff 对象
  • 内存变化: lua 内存:4194296 34304 35224(已用略减,可用略增,因触发强制 GC) sys 内存:3211584 1678228 1679316(已用大幅增加约 1.5MB,符合 1MB+512KB 分配)
  • 关键现象:日志显示 "trigger force GC",说明创建大内存时自动触发垃圾回收

步骤 3:写入数据后

  • 代码操作:向两个 zbuff 写入数据
  • 内存变化: lua 内存:4194296 34952 35224(已用略增,因字符串字面量分配) sys 内存:3211584 1678656 1679760(已用略增,因数据内容写入)
  • 结论:仅修改数据内容,不改变内存分配结构

步骤 4:调用 free() 后

  • 代码操作:释放两个 zbuff 的数据块
  • 内存变化: lua 内存:4194296 35104 35224(基本不变,对象元数据仍存在)

sys 内存:3211584 106200 1679912(已用大幅减少,恢复到接近初始状态)

  • 结论:立即释放 C 层数据块,Lua 对象元数据仍需等待 GC

步骤 5:释放引用并 GC 后

  • 代码操作:设为 nil 并手动触发 GC
  • 内存变化: lua 内存:4194296 34320 35488(已用减少,恢复到接近初始状态) sys 内存:3211584 106636 1679912(基本不变,数据块已释放)
  • 结论:彻底回收 Lua 对象元数据,内存完全释放

关键结论

zbuff 通过分离 Lua 元数据(小,存储对象信息)和 C 层数据块(大,存储实际数据),实现了高效的内存管理:

  1. 大内存块在 sys 分区/psram 分区 中分配,不占用 Lua 内存
  2. 支持手动释放数据块,避免内存泄漏
  3. 创建大内存时自动触发 GC,优化内存使用
  4. 适合处理大块二进制数据,如网络数据、文件数据等

5.2 UART 功能内存使用分析

UART 是嵌入式系统常用通信接口,内存组成:lua 配置信息(小) + sys 发送/接收缓冲区(核心)。

-- UART 内存使用示例
log.info("初始内存状态:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

local uart_id = 1  -- UART 端口号

-- 1. 初始化 UART
-- 发送和接收缓冲区在 sys 内存中分配

local setup_result = uart.setup(uart_id,  -- 串口 ID
    115200,  -- 波特率
    8,  -- 数据位
    1,  -- 停止位
    uart.NONE,  -- 校验位(uart.NONE/uart.EVEN/uart.ODD)
    uart.LSB,  -- 大小端(uart.LSB/uart.MSB)
    4096  -- 接收缓冲区大小:4KB,发送缓冲区大小固定
)
log.info("初始化 UART 后:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 2. 发送数据(字符串方式)
-- 数据首先被复制到 UART 发送缓冲区(sys 内存)
-- 然后由硬件自动发送
local send_data = string.rep("test_data_", 100)  -- 约 900 字节的数据
log.info("发送数据前:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 注册发送完成回调
local function uart_sent_callback(id)
    log.info("UART 发送完成回调:", id)
    -- 发送完成后,发送缓冲区会自动释放
    log.info("发送完成后内存状态:")
    log.info("lua:", rtos.meminfo("lua"))
    log.info("sys:", rtos.meminfo("sys"))
end

-- 注册发送完成事件回调
uart.on(uart_id, "sent", uart_sent_callback)

-- 数据复制到发送缓冲区的操作在 uart.write() 调用中执行
-- 这一步会将 send_data 字符串复制到 UART 发送缓冲区(sys 内存)
uart.write(uart_id, send_data)
log.info("调用 uart.write() 后(数据已复制到发送缓冲区):")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 等待发送完成回调(实际使用中应在回调中处理,这里为演示暂停)
sys.wait(1000)  -- 等待发送完成

-- 3. 发送数据(zbuff 方式)
-- 使用 zbuff 发送数据,减少内存拷贝
local txbuff = zbuff.create(1024)
log.info("创建发送 zbuff 后:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 向 zbuff 写入数据
local write_len = txbuff:write("Hello from zbuff!")
log.info("使用 zbuff 发送数据:")

-- 使用 uart.tx() 发送 zbuff 数据
-- 此方式直接使用 zbuff 数据,减少一次内存拷贝
local tx_result = uart.tx(uart_id, txbuff)
log.info("调用 uart.tx() 后:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 等待发送完成回调
sys.wait(1000)  -- 等待发送完成

log.info("zbuff 发送完成后:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 4. 接收数据回调
-- 接收数据存储在 UART 接收缓冲区(sys 内存)
local rxbuff = zbuff.create(4096)
log.info("创建接收 zbuff 后:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

local function uart_receive_callback(id, len)
    log.info("UART 接收数据回调,可用长度:", len)
    -- 从 UART 接收缓冲区读取数据到 zbuff
    local read_len = uart.rx(id, rxbuff)
    if read_len > 0 then
        log.info("实际读取长度:", read_len)
        log.info("接收数据:", rxbuff:toStr(0, read_len))
        -- 接收数据后,UART 接收缓冲区会自动清空
        log.info("接收数据后内存状态:")
        log.info("lua:", rtos.meminfo("lua"))
        log.info("sys:", rtos.meminfo("sys"))

        -- 重置 zbuff 指针,准备下次接收
        rxbuff:seek(0)
    end
end

-- 注册接收回调
uart.on(uart_id, "receive", uart_receive_callback)

-- 5. 释放资源
-- 释放 zbuff 资源
txbuff:free()
rxbuff:free()
log.info("释放 zbuff 后:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 6. 关闭串口
-- 使用 uart.close() 函数关闭串口,释放串口相关资源
uart.close(uart_id)
log.info("关闭串口后:")
log.info("lua:", rtos.meminfo("lua"))
log.info("sys:", rtos.meminfo("sys"))

-- 注意:uart.close() 函数用于关闭串口,释放相关资源
-- 调用后串口将不再可用,需要重新调用 uart.setup() 才能再次使用

内存使用分析:

步骤 1:初始内存状态

lua 内存:4194296 38176 38304(总大小 4MB,已用 38176B,可用 38304B)

sys 内存:3211584 104760 105880(总大小 3.2MB,已用 104760B,可用 105880B)

步骤 2:初始化 UART 后

代码操作:调用 uart.setup() 配置串口参数

内存变化:

结论:初始化过程分配了接收缓冲区和内部数据结构

步骤 3:发送数据前

代码操作:创建发送数据字符串

内存变化:

步骤 4:调用 uart.write() 后(数据已复制到发送缓冲区)

代码操作:注册发送完成回调并调用 uart.write()

内存变化:

关键现象:数据复制到发送缓冲区的操作在 uart.write() 调用中执行

步骤 5:发送完成后(回调中)

代码操作:发送完成回调触发

内存变化:

结论:发送完成后,发送缓冲区自动释放

步骤 6:创建发送 zbuff 后

代码操作:创建 1024B 的发送 zbuff

内存变化:

步骤 7:调用 uart.tx() 后

代码操作:使用 zbuff 发送数据

内存变化:

结论:zbuff 方式发送无额外内存拷贝,效率更高

步骤 8:zbuff 发送完成后

代码操作:等待发送完成

内存变化:

步骤 9:创建接收 zbuff 后

代码操作:创建 4096B 的接收 zbuff

内存变化:

步骤 10:释放 zbuff 后

代码操作:释放发送和接收 zbuff

内存变化:

步骤 11:关闭串口后

代码操作:调用 uart.close() 关闭串口

内存变化:

关键结论

内存分配结构:UART 主要使用 sys 内存作为收发缓冲区,lua 内存仅存储配置对象和回调函数

发送方式对比:

资源管理:

内存优化建议:大数据发送优先使用 zbuff 方式,减少内存拷贝和碎片

5.3 MQTT 功能内存使用分析

MQTT 是物联网设备常用的轻量级通信协议,在 LuatOS 中,MQTT 客户端的内存使用涉及 lua 和 sys 内存分配:

  • lua 内存:主要存储客户端对象、配置信息、订阅信息和临时消息对象,占用较小但管理核心逻辑
  • sys 内存:主要存储连接和收发缓冲区,是内存使用的核心部分,占用较大且随消息大小动态变化
-- MQTT 内存使用示例
log.info("初始内存状态:")
log.info("lua:", rtos.meminfo("lua"))  -- 记录Lua虚拟机内存使用
log.info("sys:", rtos.meminfo("sys"))  -- 记录系统内存使用

-- 配置 MQTT 服务器信息
local SERVER_ADDR = "lbsmqtt.airm2m.com"
local SERVER_PORT = 1884
local USERNAME = "test"
local PASSWORD = "test"
local CLIENT_ID = "luatos_test_device"

-- 定义 MQTT 客户端回调函数
local function mqtt_client_cb(mqtt_client, event, data, payload, metas)
    log.info("MQTT 事件:", event, data, payload and #payload or "nil")

    -- 连接成功事件
    if event == "conack" then
        log.info("MQTT 连接成功")
        -- 连接成功后可以订阅主题
        -- 内存影响:
        --   - lua内存:增加约592B,存储订阅信息
        --   - sys内存:基本不变
        mqtt_client:subscribe("device/commands", 0)
        -- 对应内存分析:步骤 6,订阅主题后
        log.info("订阅主题后内存:")
        log.info("lua:", rtos.meminfo("lua"))
        log.info("sys:", rtos.meminfo("sys"))

    end

    -- 接收到消息事件
    if event == "recv" then
        log.info("MQTT 接收消息:", data, #payload, "字节")
        -- 内存影响:接收到消息时的临时内存使用
        -- 对应内存分析:接收消息后,检查消息接收的内存变化
        log.info("接收消息后内存:")
        log.info("lua:", rtos.meminfo("lua"))
        log.info("sys:", rtos.meminfo("sys"))

        -- 对于大消息,可以使用 zbuff 处理
        -- 内存影响:创建临时zbuff对象,处理完成后释放
        if #payload > 1024 then -- 大于 1KB 的消息
            local buff = zbuff.create(#payload)  -- 创建对应大小的zbuff
            buff:write(payload)  -- 写入数据
            -- 处理数据...
            buff:free()  -- 释放zbuff数据块
        end
    end
end

-- MQTT 客户端需要在 task 中运行
local function mqtt_test_task()
    -- 1. 创建 MQTT 客户端
    -- 参数:nil, 服务器地址, 服务器端口
    -- 内存影响:
    --   - lua内存:增加约38120B,存储客户端对象和内部数据结构
    --   - sys内存:增加约3848B,分配基础结构
    local mqtt_client = mqtt.create(nil, SERVER_ADDR, SERVER_PORT)
    -- 对应内存分析:步骤 3,创建 MQTT 客户端后
    log.info("创建 MQTT 客户端后:")
    log.info("lua:", rtos.meminfo("lua"))
    log.info("sys:", rtos.meminfo("sys"))

    -- 2. 配置认证信息
    -- 内存影响:
    --   - lua内存:增加约264B,存储认证配置
    --   - sys内存:基本不变
    mqtt_client:auth(CLIENT_ID, USERNAME, PASSWORD, true)
    -- 对应内存分析:步骤 4,配置认证信息后
    log.info("配置认证信息后:")
    log.info("lua:", rtos.meminfo("lua"))
    log.info("sys:", rtos.meminfo("sys"))

    -- 3. 注册事件回调
    -- 内存影响:无显著内存变化,仅注册回调函数
    mqtt_client:on(mqtt_client_cb)

    -- 4. 设置 keepalive
    -- 内存影响:无显著内存变化,仅设置参数
    mqtt_client:keepalive(30)

    -- 5. 连接 MQTT 服务器
    -- 内存影响:
    --   - lua内存:增加约160B,存储连接状态
    --   - sys内存:增加约33204B,分配连接和收发缓冲区
    local connect_result = mqtt_client:connect()
    -- 对应内存分析:步骤 5,连接 MQTT 服务器后
    log.info("连接 MQTT 服务器后:")
    log.info("lua:", rtos.meminfo("lua"))
    log.info("sys:", rtos.meminfo("sys"))

    -- 等待连接成功(最多10秒)
    -- 对应内存分析:步骤 2,网络连接就绪,无明显内存变化
    local connected = false
    local start_time = os.time()
    while not connected and os.time() - start_time < 10 do
        sys.wait(1000)
        -- 检查连接状态(实际应用中通过事件判断)
        connected = true -- 简化示例,实际应通过事件回调判断
    end

    if connected then
        -- 6. 发布小消息
        -- 内存影响:
        --   - lua内存:增加约376B,存储消息对象
        --   - sys内存:减少约472B,可能是临时对象释放
        local small_msg = "{\"temp\":25.5,\"humidity\":60}" -- 约 30 字节
        -- 对应内存分析:步骤 7,发布小消息前
        log.info("发布小消息前:")
        log.info("lua:", rtos.meminfo("lua"))
        log.info("sys:", rtos.meminfo("sys"))

        -- 内存影响:
        --   - lua内存:增加约176B,消息发送相关结构
        --   - sys内存:增加约380B,消息发送缓冲区
        local publish_result = mqtt_client:publish("sensor/data", small_msg, 0)
        -- 对应内存分析:步骤 8,发布小消息后
        log.info("发布小消息后:")
        log.info("lua:", rtos.meminfo("lua"))
        log.info("sys:", rtos.meminfo("sys"))

        -- 7. 发布大消息
        -- 内存影响:
        --   - lua内存:增加约10328B,存储大消息对象
        --   - sys内存:基本不变
        local large_msg = string.rep("0123456789", 1000) -- 约 10KB 数据
        -- 对应内存分析:步骤 9,发布大消息前
        log.info("发布大消息前:")
        log.info("lua:", rtos.meminfo("lua"))
        log.info("sys:", rtos.meminfo("sys"))

        -- 内存影响:
        --   - lua内存:增加约208B,消息发送相关结构
        --   - sys内存:增加约12300B,大消息发送缓冲区
        local publish_result = mqtt_client:publish("sensor/large_data", large_msg, 0)
        -- 对应内存分析:步骤 10,发布大消息后
        log.info("发布大消息后:")
        log.info("lua:", rtos.meminfo("lua"))
        log.info("sys:", rtos.meminfo("sys"))

        -- 运行一段时间
        sys.wait(5000)
    end

    -- 8. 断开连接
    -- 内存影响:
    --   - lua内存:增加约376B,断开连接相关操作
    --   - sys内存:减少约12404B,释放连接相关资源
    mqtt_client:disconnect()
    -- 对应内存分析:步骤 11,断开 MQTT 连接后
    log.info("断开 MQTT 连接后:")
    log.info("lua:", rtos.meminfo("lua"))
    log.info("sys:", rtos.meminfo("sys"))

    -- 9. 关闭客户端并释放资源
    -- 内存影响:关闭客户端,准备释放资源
    mqtt_client:close()
    mqtt_client = nil -- 断开引用,等待GC回收

    -- 重复执行多次垃圾回收操作,每次间隔1秒
    -- 目的是彻底释放MQTT客户端占用的资源,确保内存完全回收
    -- 内存影响:
    --   - lua内存:减少约49272B,客户端对象和相关结构完全释放
    --   - sys内存:减少约33436B,释放所有 MQTT 相关资源
    -- 多次执行垃圾回收并间隔等待,确保:
    -- 1. Lua的垃圾回收器采用增量式标记-清除算法,多次调用 collectgarbage("collect") 是为了确保更彻底地回收垃圾
    -- 2. mqtt是异步操作,使用非阻塞函数和事件回调机制来处理网络操作。某些资源的释放可能依赖于异步操作,需要等待系统完成相关处理
    -- 3. 多次回收有助于更有效地整理内存碎片,提高内存使用效率
    for i = 1, 5 do
        collectgarbage("collect")  -- 执行垃圾回收
        sys.wait(1000)             -- 等待1秒,让系统有时间处理垃圾回收
    end

    -- 对应内存分析:步骤 12,关闭客户端并多次 GC 后
    -- 最终状态:
    --   - lua内存:约40248B(接近初始状态的38920B)
    --   - sys内存:约108068B(接近初始状态的104760B)
    log.info("关闭客户端并 GC 后:")
    log.info("lua:", rtos.meminfo("lua"))
    log.info("sys:", rtos.meminfo("sys"))

    log.info("MQTT 客户端资源已释放")
end

-- 创建并启动 MQTT 测试任务
sys.taskInit(mqtt_test_task)

内存使用分析:

步骤 1:初始内存状态

lua 内存:4194296 38920 38920(总大小 4MB,已用 38920B,可用 38920B)

sys 内存:3211584 104760 105880(总大小 3.2MB,已用 104760B,可用 105880B)

步骤 2:网络连接就绪

代码操作:等待网络连接就绪,循环检查 socket.adapter(socket.dft())

内存变化:无明显变化,等待过程不分配额外内存

步骤 3:创建 MQTT 客户端后

代码操作:调用 mqtt.create(nil, SERVER_ADDR, SERVER_PORT) 创建客户端

内存变化:

结论:创建客户端时主要在 lua 内存中分配客户端对象和配置

步骤 4:配置认证信息后

代码操作:调用 mqtt_client:auth(CLIENT_ID, USERNAME, PASSWORD, true) 配置认证

内存变化:

结论:认证配置主要占用 lua 内存,增加较少

步骤 5:连接 MQTT 服务器后

代码操作:调用 mqtt_client:connect() 连接服务器

内存变化:

结论:连接服务器时主要在 sys 内存中分配连接和收发缓冲区

步骤 6:订阅主题后

代码操作:调用 mqtt_client:subscribe("device/commands", 0) 订阅主题

内存变化:

结论:订阅主题主要占用 lua 内存,增加较少

步骤 7:发布小消息前

代码操作:创建约 30 字节的小消息

内存变化:

步骤 8:发布小消息后

代码操作:调用 mqtt_client:publish("sensor/data", small_msg, 0) 发布小消息

内存变化:

步骤 9:发布大消息前

代码操作:创建约 10KB 的大消息(string.rep("0123456789", 1000))

内存变化:

结论:大消息创建时在 lua 内存中分配临时消息对象

步骤 10:发布大消息后

代码操作:调用 mqtt_client:publish("sensor/large_data", large_msg, 0) 发布大消息

内存变化:

结论:大消息发布时在 sys 内存中分配较大的发送缓冲区

步骤 11:断开 MQTT 连接后

代码操作:调用 mqtt_client:disconnect() 断开连接

内存变化:

结论:断开连接时释放部分 sys 内存中的连接资源

步骤 12:关闭客户端并多次 GC 后

代码操作:调用 mqtt_client:close() 关闭客户端,设置 mqtt_client = nil,多次触发 GC

内存变化:

结论:通过多次 GC,lua 内存几乎完全恢复到初始状态(仅增加约 1328B,由网络或者其他任务导致),sys 内存也基本恢复

关键结论

内存分配结构:MQTT 客户端内存使用涉及 lua 和 sys 内存:

内存使用特点:

使用建议:

5.4 Socket 功能内存使用分析

Socket 是网络通信的基础接口,在 LuatOS 中,Socket 的内存使用主要涉及 sys 内存,用于存储发送和接收缓冲区,同时也会使用 lua 内存存储 Socket 对象和配置。LuatOS 的 socket 库是异步非阻塞 API:

-- Socket内存使用情况完整演示

-- 加载libnet库
local libnet = require "libnet"

-- 配置
local SERVER_ADDR = "112.125.89.8"
local SERVER_PORT = 33768
local TASK_NAME = "socket_mem_demo"

-- 内存记录函数
local function log_memory_info(step)
    log.info("=== " .. step .. " ===")
    log.info("lua:", rtos.meminfo("lua"))
    log.info("sys:", rtos.meminfo("sys"))
end

-- 消息回调函数(libnet需要)
local function mem_demo_callback(msg)
    log.info("mem_demo回调")
end

-- 主任务函数
local function socket_memory_task()
    -- 等待网络就绪
    sys.waitUntil("IP_READY", 30000)
    -- 记录初始内存
    -- 对应内存分析:初始状态,记录系统启动后的内存基线
    log_memory_info("初始状态")

    -- 1. 创建socket
    -- 内存影响:
    --   - lua内存:增加约392B,存储socket对象和相关配置信息
    --   - sys内存:增加约108B,分配socket控制块和基础结构
    local socket_client = socket.create(nil, TASK_NAME)
    if not socket_client then
        log.error("创建socket失败")
        sys.cleanMsg(TASK_NAME)
        sys.taskDel(TASK_NAME)
        return
    end
    -- 对应内存分析:创建socket后,检查socket对象分配的内存
    log_memory_info("创建socket后")

    -- 2. 配置socket
    -- 内存影响:配置socket参数,无显著内存变化
    if not socket.config(socket_client) then
        log.error("配置socket失败")
        socket.release(socket_client)
        sys.cleanMsg(TASK_NAME)
        sys.taskDel(TASK_NAME)
        return
    end

    -- 3. 创建接收缓冲区
    -- 内存影响:
    --   - lua内存:增加约248B,存储zbuff对象元数据
    --   - sys内存:增加约1024B,分配1KB的实际接收缓冲区
    local rx_buff = zbuff.create(1024)
    -- 对应内存分析:创建缓冲区后,检查1KB缓冲区的内存分配
    log_memory_info("创建缓冲区后")

    -- 4. 连接服务器
    -- 内存影响:
    --   - lua内存:增加约1032B,存储连接状态、服务器地址信息
    --   - sys内存:减少约284B,内核优化临时缓冲区使用
    log.info("正在连接服务器...")
    local connect_result = libnet.connect(TASK_NAME, 10000, socket_client, SERVER_ADDR, SERVER_PORT)

    if connect_result then
        log.info("连接成功")
        -- 对应内存分析:连接服务器后,检查连接过程的内存变化
        log_memory_info("连接服务器后")

        -- 5. 发送数据
        -- 内存影响:
        --   - lua内存:增加约816B,存储临时发送数据对象和发送状态
        --   - sys内存:减少约80B,发送缓冲区数据被网络层处理后释放
        local test_data = "内存测试数据 " .. os.time()
        local send_result = libnet.tx(TASK_NAME, 5000, socket_client, test_data)

        if send_result then
            log.info("发送成功:", test_data)
            -- 对应内存分析:发送数据后,检查数据发送过程的内存变化
            log_memory_info("发送数据后")
        else
            log.warn("发送失败")
        end

        -- 6. 等待并接收数据
        -- 内存影响:
        --   - lua内存:增加约544B,存储接收到的数据对象和接收状态
        --   - sys内存:增加约148B,内核处理接收到的数据时的临时缓冲区使用
        -- 等待5秒,期间持续检查数据,5秒后无论是否收到数据都继续执行
        local wait_start = os.time()
        local has_received_data = false
        while os.time() - wait_start < 5 do
            -- 检查是否有数据
            local rx_result = socket.rx(socket_client, rx_buff)
            if rx_result then
                if rx_buff:used() > 0 and not has_received_data then
                    log.info("recv data len", rx_buff:used())
                    -- 对应内存分析:接收数据后,检查数据接收过程的内存变化
                    log_memory_info("接收数据后")
                    has_received_data = true
                    -- 收到数据后不立即跳出循环,继续等待剩余时间
                end
            else
                log.error("socket.rx失败")
                -- socket.rx失败也不立即跳出循环,继续等待剩余时间
            end
            sys.wait(200)
        end
        log.info("等待5秒接收数据完成")

        -- 7. 关闭连接
        -- 内存影响:
        --   - lua内存:增加约4768B,连接关闭过程中产生的临时对象和状态信息
        --   - sys内存:增加约188B,TCP四次挥手过程中的状态管理开销
        log.info("正在关闭连接...")
        libnet.close(TASK_NAME, 5000, socket_client)
        -- 对应内存分析:关闭连接后,检查连接关闭过程的内存变化
        log_memory_info("关闭连接后")
    else
        log.error("连接失败")
    end

    -- 8. 释放资源
    -- 内存影响:
    --   - lua内存:增加约168B,资源释放过程中的临时操作
    --   - sys内存:减少约1136B,释放socket控制块和1KB接收缓冲区
    if socket_client then
        socket.release(socket_client)  -- 释放socket控制块
        socket_client = nil  -- 断开引用,等待GC回收
    end

    if rx_buff then
        rx_buff:free()  -- 释放zbuff数据块
        rx_buff = nil  -- 断开引用,等待GC回收
    end

    -- 对应内存分析:释放资源后,检查资源释放的内存变化
    log_memory_info("释放资源后")

    -- 9. 强制垃圾回收
    -- 重复执行多次垃圾回收操作,每次间隔1秒
    -- 目的是彻底释放socket客户端占用的资源,确保内存完全回收
    -- 内存影响:
    --   - lua内存:减少约9344B,回收所有临时对象、socket对象、缓冲区对象等
    --   - sys内存:减少约228B,内核清理剩余的socket相关资源
    -- 多次执行垃圾回收并间隔等待,确保:
    -- 1. Lua的垃圾回收器采用增量式标记-清除算法,多次调用 collectgarbage("collect") 是为了确保更彻底地回收垃圾
    -- 2. socket是异步操作,使用非阻塞函数和事件回调机制来处理网络操作。某些资源的释放可能依赖于异步操作,需要等待系统完成相关处理
    -- 3. 多次回收有助于更有效地整理内存碎片,提高内存使用效率
    for i = 1, 5 do
        collectgarbage("collect")  -- 执行垃圾回收
        sys.wait(1000)             -- 等待1秒,让系统有时间处理垃圾回收
    end

    -- 对应内存分析:垃圾回收后,检查内存恢复情况
    log_memory_info("垃圾回收后")

    -- 10. 内存对比总结
    -- 最终状态:
    --   - lua内存:约42800B(接近初始状态的44176B)
    --   - sys内存:约108052B(接近初始状态的108312B)
    -- 结论:通过完整的资源释放和多次垃圾回收,内存基本恢复到初始状态
    log.info("=== 内存演示完成 ===")

    -- 清理task资源
    sys.cleanMsg(TASK_NAME)
    sys.taskDel(TASK_NAME)
end

-- 使用sys.taskInitEx创建任务(libnet需要)
sys.taskInitEx(socket_memory_task, TASK_NAME, mem_demo_callback)

内存使用分析:

  1. 初始状态

代码对应:log_memory_info("初始状态")

内存数据:

分析:系统启动后的初始内存状态,此时还未创建任何 socket 相关资源

  1. 创建 socket 后

代码对应:local socket_client = socket.create(nil, TASK_NAME)

内存变化:

分析:

  1. 创建缓冲区后

代码对应:local rx_buff = zbuff.create(1024)

内存变化:

分析:

  1. 连接服务器后

代码对应:local connect_result = libnet.connect(TASK_NAME, 10000, socket_client, SERVER_ADDR, SERVER_PORT)

内存变化:

分析:

  1. 发送数据后

代码对应:local send_result = libnet.tx(TASK_NAME, 5000, socket_client, test_data)

内存变化:

分析:

  1. 接收数据后

代码对应:local rx_result = socket.rx(socket_client, rx_buff)

内存变化:

分析:

  1. 关闭连接后

代码对应:libnet.close(TASK_NAME, 5000, socket_client)

内存变化:

分析:

  1. 释放资源后

代码对应:

socket.release(socket_client) socket_client = nil rx_buff:free() rx_buff = nil

内存变化:

分析:

  1. 垃圾回收后

代码对应:

for i = 1, 5 do collectgarbage("collect") sys.wait(1000) end

内存变化:

分析:

  1. 内存恢复情况

最终状态:

分析:内存基本恢复到初始状态

关键结论

内存分配特点:

使用建议:

不再使用的 MQTT 客户端及时调用 socket.release()释放 socket 资源

使用的 zbuff 缓冲区及时调用 zbuff:free()释放缓冲区资源

lua 层的内存须进行多次垃圾回收才能完全释放

六、内存监控与分析方法

6.1 使用 rtos.meminfo() 进行系统监控

rtos.meminfo() 是 LuatOS 提供的系统级内存信息查询函数,可以获取详细的内存使用统计信息。通过定时调用这个函数并记录返回值,luatools 可以绘制出内存使用曲线,帮助开发者直观地了解内存的变化趋势。

注意

  • luatools 3.1.12:提供内存曲线图和 CSV 导出功能
  • 定时采样:可设置定时器定期采集内存数据
  • 注意需要使用 V2020 版本及以上的版本才行

需要监控内存使用时将下面代码加入自己脚本中间就行

sys.timerLoopStart(function()
    log.info("mem.lua", rtos.meminfo())
    log.info("mem.sys", rtos.meminfo("sys"))
    --log.info("mem.psram", rtos.meminfo("psram"))  --需要时打开
end, 3000)

6.2 内存泄漏案例分析

6.2.1 内存泄漏 Demo 脚本

以下是一个简洁的内存泄漏演示:定时器未正确停止,导致对象无法被回收。

-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
sys.timerLoopStart(function()
    log.info("mem.lua", rtos.meminfo())
    log.info("mem.sys", rtos.meminfo("sys"))
    log.info("mem.psram", rtos.meminfo("psram"))
end, 3000)

-- 格式化内存输出
local function format_memory()
    local total, used, max_used = rtos.meminfo("lua")
    return string.format("使用:%.1fKB/%.1fKB(%.1f%%) 峰值:%.1fKB",
        used/1024, total/1024, (used/total)*100, max_used/1024)
end

-- 泄漏的数据收集器
local function create_leaky_collector(id)
    local collector = {
        id = id,
        data = {},  -- 存储数据
        count = 0
    }

    -- 定时器持续运行,引用collector导致无法回收
    collector.timer_id = sys.timerLoopStart(function()
        collector.count = collector.count + 1
        collector.data[#collector.data + 1] = {
            time = os.time(),
            value = math.random(100),
            payload = string.rep("X", 100)  -- 100字节数据
        }

        -- 每100次记录一次
        if collector.count % 100 == 0 then
            log.info("收集器", id, "数据:", collector.count, "内存:", format_memory())
        end
    end, 100)  -- 每100ms执行一次

    log.info("创建收集器", id)
    return collector
end

-- 主程序:连续创建收集器,但不释放定时器
sys.taskInit(function()
    log.info("=== 内存泄漏演示开始 ===")
    log.info("初始内存:", format_memory())

    local collectors = {}

    -- 每2秒创建一个新收集器
    for i = 1, 10 do
        log.info("\n--- 创建第", i, "个收集器 ---")

        local collector = create_leaky_collector("col_" .. i)
        collectors[i] = collector

        log.info("当前内存:", format_memory())
        sys.wait(2000)
    end

    log.info("\n=== 所有收集器已创建 ===")
    log.info("收集器数量:", #collectors)
    log.info("当前内存:", format_memory())

    -- 尝试释放引用(但定时器还在运行)
    log.info("\n尝试释放collectors表...")
    collectors = nil
    collectgarbage("collect")
    log.info("GC后内存:", format_memory())

    -- 观察内存变化
    for i = 1, 5 do
        sys.wait(3000)
        log.info("等待", i*3, "秒后内存:", format_memory())
    end

    log.info("=== 演示结束 ===")
    log.info("内存不会下降,因为定时器仍在运行")
end)

运行结果:

6.2.2 泄漏现象与原因

现象:

  • Lua 内存持续线性增长,即使对象不再使用
  • 内存使用率从初始约 10% 逐渐上升至 90%+
  • collectgarbage("collect") 调用后内存不回落
  • 最终系统内存耗尽,可能导致崩溃

根本原因: 定时器是 C 层资源,需手动管理生命周期。当定时器闭包引用 Lua 对象,且未调用 sys.timerStop() 时:

  1. 定时器持续运行 → 闭包始终存活
  2. 闭包引用 self 对象 → self 无法被 GC 回收
  3. self 对象包含数据缓冲区 → 所有数据无法释放
  4. 循环创建新对象 → 内存持续累积,最终耗尽

6.2.3 修复方案

在 destroy_collector 函中调用 sys.timerStop 正确停止定时器并解除引用:

-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
sys.timerLoopStart(function()
    log.info("mem.lua", rtos.meminfo())
    log.info("mem.sys", rtos.meminfo("sys"))
    log.info("mem.psram", rtos.meminfo("psram"))
end, 3000)

-- 格式化内存输出
local function format_memory()
    local total, used, max_used = rtos.meminfo("lua")
    return string.format("使用:%.1fKB/%.1fKB(%.1f%%) 峰值:%.1fKB",
        used/1024, total/1024, (used/total)*100, max_used/1024)
end

-- 泄漏的数据收集器
local function create_leaky_collector(id)
    local collector = {
        id = id,
        data = {},  -- 存储数据
        count = 0
    }

    -- 定时器持续运行,引用collector导致无法回收
    collector.timer_id = sys.timerLoopStart(function()
        collector.count = collector.count + 1
        collector.data[#collector.data + 1] = {
            time = os.time(),
            value = math.random(100),
            payload = string.rep("X", 100)  -- 100字节数据
        }

        -- 每100次记录一次
        if collector.count % 100 == 0 then
            log.info("收集器", id, "数据:", collector.count, "内存:", format_memory())
        end
    end, 100)  -- 每100ms执行一次

    log.info("创建收集器", id)
    return collector
end

-- 收集器销毁函数
local function destroy_collector(collector)
    if collector and collector.timer_id then
        sys.timerStop(collector.timer_id)  -- 关键修复:停止定时器
        collector.timer_id = nil
        collector.data = nil
        log.info("已停止收集器", collector.id)
    end
    return nil
end

-- 主程序:创建收集器,但会正确释放
sys.taskInit(function()
    log.info("=== 内存泄漏修复演示开始 ===")
    log.info("初始内存:", format_memory())

    local collectors = {}

    -- 每2秒创建一个新收集器
    for i = 1, 10 do
        log.info("\n--- 创建第", i, "个收集器 ---")

        local collector = create_proper_collector("col_" .. i)
        collectors[i] = collector

        log.info("当前内存:", format_memory())
        sys.wait(2000)
    end

    log.info("\n=== 所有收集器已创建 ===")
    log.info("收集器数量:", #collectors)
    log.info("当前内存:", format_memory())

    -- 让收集器继续运行一段时间
    log.info("\n--- 收集器继续运行10秒 ---")
    for i = 1, 5 do
        sys.wait(2000)
        log.info("运行", i*2, "秒后内存:", format_memory())
    end

    -- 正确释放所有收集器(关键修复)
    log.info("\n正确释放所有收集器...")
    for i = 1, #collectors do
        collectors[i] = destroy_collector(collectors[i])
    end

    collectors = nil  -- 释放引用

    -- 执行GC
    collectgarbage("collect")
    sys.wait(100)  -- 等待GC完成
    collectgarbage("collect")  -- 再次GC确保清理
    log.info("GC后内存:", format_memory())


    log.info("=== 演示结束 ===")
    log.info("内存已正确回收,无内存泄漏")
end)

修复说明:

  1. 添加收集器销毁函数(destroy_collector)
local function destroy_collector(collector)
    if collector and collector.timer_id then
        sys.timerStop(collector.timer_id)  -- 关键修复:停止定时器
        collector.timer_id = nil
        collector.data = nil
        log.info("已停止收集器", collector.id)
    end
    return nil
end
  1. 修改主程序的释放逻辑
-- 旧代码:
-- collectors = nil  -- 只释放引用,不停止定时器

-- 新代码:
for i = 1, #collectors do
    collectors[i] = destroy_collector(collectors[i])  -- 先停止定时器
end
collectors = nil  -- 然后释放引用
  1. 改进 GC 调用
collectgarbage("collect")
sys.wait(100)  -- 给GC一点时间
collectgarbage("collect")  -- 再次调用确保清理

6.2.4 关键结论

  1. 常见泄漏场景

  2. 定时器未停止:最常见的内存泄漏原因

  3. 事件监听器未移除:回调函数持续持有对象引用
  4. 全局/静态变量累积:意外将临时对象存入全局表
  5. 循环引用未打破:对象间相互引用,且无外部访问

  6. 预防措施

  7. 配对原则:创建 与 释放 必须成对出现

local timer = sys.timerStart(...)  -- 创建
sys.timerStop(timer)               -- 必须释放
  • 及时置空:不再使用的对象引用设为 nil
  • 监控内存:使用 rtos.meminfo("lua") 定期检查内存变化

七、常见内存问题及分析

7.1 zbuff 内存操作

zbuff 简介:zbuff 是 LuatOS 中用于直接操作二进制内存数据的库,类似于 C 语言中的内存指针,提供高效的内存管理和操作能力。

核心优势:

  1. 动态内存管理:

  2. 自动灵活分配内存

  3. 支持动态调整缓冲区大小(resize)
  4. 提供手动释放机制(free),可立即回收内存
  5. 高效数据操作:

  6. 原地修改数据,无需创建新对象

  7. 支持多种数据类型的直接读写(u8/i16/u32/f32 等)
  8. 提供类似 C 语言的内存操作接口(set/copy/del 等)
  9. 低内存开销:

  10. 避免 Lua 字符串不可变性导致的内存碎片

  11. 适合处理大块二进制数据(如图像、网络包)
  12. 内存操作效率远高于 Lua 原生类型
  13. 安全可靠:

  14. 支持格式化的打包/解包操作

  15. 自动管理 C 层内存,降低内存泄漏风险

适用场景:

  • 处理大量二进制数据(如图像、音频、网络帧)
  • 实现高效的协议解析和封装
  • 需要频繁修改数据内容的场景
  • 对内存占用敏感的应用

7.1 字符串拼接内存爆炸

问题:Lua 字符串是不可变的,每次拼接操作都会创建新的字符串对象,导致内存占用急剧增加。

解决方案:

  • 使用 table.concat 替代 .. 运算符进行大量字符串拼接。
  • 对于二进制数据或需要频繁修改的场景,推荐使用 zbuff 进行内存操作,可避免字符串拼接带来的内存开销
  • zbuff 支持原地修改数据,无需创建中间对象,内存效率更高
-- 低效的字符串拼接
local function bad_concat(count)
    local s = ""
    for i = 1, count do
        s = s .. "line " .. i .. "\n"
    end
    return s
end

-- 高效的字符串拼接
local function good_concat(count)
    local parts = {}
    for i = 1, count do
        parts[#parts + 1] = "line "
        parts[#parts + 1] = i
        parts[#parts + 1] = "\n"
    end
    return table.concat(parts)
end

-- 性能对比
local bad_time = os.time()
bad_concat(10000)
bad_time = os.time() - bad_time

local good_time = os.time()
good_concat(10000)
good_time = os.time() - good_time

log.info("String Concat Performance",
    "Bad:", bad_time, "ms",
    "Good:", good_time, "ms",
    "Improvement:", bad_time / good_time, "x")

7.2 大表内存占用过高

问题:向表中插入大量元素时,表的内存占用会显著增加,尤其是在频繁插入和删除操作的场景下。

解决方案:

  1. 及时清理不再使用的表元素
  2. 考虑使用分块存储策略

7.3 大文件读取内存不足

问题:一次性读取大文件会占用大量内存,可能导致内存不足错误。

解决方案:采用分块读取或流式处理方式。

-- 分块读取大文件
local function read_file_in_chunks(file_path, chunk_size, callback)
    local file = io.open(file_path, "rb")
    if not file then
        return nil, "Cannot open file"
    end

    local total_size = 0
    while true do
        local chunk = file:read(chunk_size)
        if not chunk then break end
        total_size = total_size + #chunk
        if callback then
            callback(chunk, total_size)
        end
    end

    file:close()
    return total_size
end

-- 使用示例
read_file_in_chunks("/lua/large_file.txt", 8192, function(chunk, total)
    -- 处理每个数据块
    process_chunk(chunk)
end)

7.4 为什么还有剩余内存却提示内存不足?

问题:collectgarbage("count") 返回的是正在使用的内存,不包括已被释放但尚未被虚拟机重新利用的碎片内存。当需要分配大块连续内存时,即使总可用内存足够,也可能因为碎片化而无法找到满足要求的连续空间。

解决方案:

  1. 减少内存碎片:避免频繁的小内存分配操作
  2. 及时执行垃圾回收:在内存操作的关键节点连续调用 collectgarbage("collect") 2-3 次,连续调用可确保所有垃圾被彻底回收,减少内存碎片。

八、内存优化建议

8.1 代码编写最佳实践

  1. 优先使用局部变量:局部变量的生命周期短,更容易被垃圾回收器回收。
-- 错误做法:使用全局变量
   global_var = "这是一个全局变量"

   -- 正确做法:使用局部变量
   local local_var = "这是一个局部变量"
  1. 及时释放大对象引用:大对象使用完毕后,将其引用设为 nil,并调用 collectgarbage("collect")。
-- 创建大对象
   local big_table = {}
   for i = 1, 10000 do
       big_table[i] = string.rep("x", 100)
   end

   -- 使用完毕后释放
   big_table = nil  -- 释放引用
   collectgarbage("collect")  -- 触发GC
  1. 避免循环引用:两个或多个对象相互引用会形成循环引用,可能导致垃圾回收器无法正确识别并回收这些对象,应尽量避免这种设计。
-- 错误做法:形成循环引用
   local obj1 = {}
   local obj2 = {}
   obj1.ref = obj2
   obj2.ref = obj1

   -- 正确做法:打破循环引用
   obj1.ref = nil
   obj2.ref = nil
  1. 减少全局变量使用:全局变量存储在全局表中,生命周期与程序相同,应尽量减少使用。
-- 错误做法:直接使用全局变量
global_config = { timeout = 1000 }

-- 正确做法:封装为局部变量或模块 local config = { timeout = 1000 }
  1. 优化表结构:合理设计表结构,避免稀疏数组和混合键类型,提高内存使用效率。
-- 错误做法:混合键类型且数组稀疏
local bad_table = {
    name = "test",       -- 字符串键
    value = 30,          -- 字符串键
    [1] = 10,
    [100] = 20,          -- 索引不连续
    [200] = 40           
}

-- 正确做法1:数组部分和哈希部分分离
local good_table = {
    -- 数组部分(连续整数索引,无nil值)
   10, 20, 30, 40,

   -- 哈希部分(字符串键)
    name = "test",
    value = 50
}

-- 正确做法2:使用两个专门的表
local array_part = {10, 20, 30, 40}        -- 纯数组,高效
local hash_part = {name = "test", value = 50} -- 纯哈希表

实际举例:

-- Lua表结构内存使用测试

local function print_memory_info(description)
    local total_lua, used_lua, max_used_lua = rtos.meminfo("lua")
    log.info("内存测试", string.format("%s - Lua内存使用量: %d 字节", description, used_lua))
    return used_lua
end

local function test_memory_usage()
    -- 初始内存状态
    local initial_memory = print_memory_info("初始状态")

    log.info("内存测试", "\n=== 测试1: 错误做法 - 稀疏数组 + 混合键类型 ===")
    local sparse_mixed = {
        [1] = "value1",
        [1000] = "value2",  -- 稀疏数组
        [2000] = "value3",
        [3000] = "value4",
        [4000] = "value5",
        [5000] = "value6",
        [6000] = "value7",
        [7000] = "value8",
        [8000] = "value9",
        [9000] = "value10", -- 共10个数值元素
        name = "test",       -- 混合键类型
        value = 50
    }
    local sparse_memory = print_memory_info("创建稀疏混合表后")
    log.info("内存测试", string.format("内存增加: %d 字节", sparse_memory - initial_memory))

    -- 清理测试1的数据
    sparse_mixed = nil
    collectgarbage()
    initial_memory = print_memory_info("垃圾回收后")

    log.info("内存测试", "\n=== 测试2: 正确做法1 - 连续数组 + 混合键类型 ===")
    local dense_mixed = {
        "value1", "value2", "value3", "value4", "value5",
        "value6", "value7", "value8", "value9", "value10",  -- 连续数组(10个元素)
        name = "test",
        value = 50
    }
    local dense_mixed_memory = print_memory_info("创建密集混合表后")
    log.info("内存测试", string.format("内存增加: %d 字节", dense_mixed_memory - initial_memory))

    -- 清理测试2的数据
    dense_mixed = nil
    collectgarbage()
    initial_memory = print_memory_info("垃圾回收后")

    log.info("内存测试", "\n=== 测试3: 正确做法2 - 分离的数组和哈希表 ===")
    local array_part = {
        "value1", "value2", "value3", "value4", "value5",
        "value6", "value7", "value8", "value9", "value10"  -- 纯数组(10个元素)
    }
    local hash_part = {
        name = "test",
        value = 50
    }  -- 纯哈希表
    local separate_memory = print_memory_info("创建分离的数组和哈希表后")
    log.info("内存测试", string.format("内存增加: %d 字节", separate_memory - initial_memory))

    -- 清理测试3的数据
    array_part = nil
    hash_part = nil
    collectgarbage()
    print_memory_info("所有测试完成后的最终状态")
end

-- 运行测试
test_memory_usage()

测试结果:

测试结果验证了:

  • 稀疏数组确实浪费内存 (测试 1 > 测试 2)
  • 连续数组比稀疏数组高效 (测试 2 < 测试 1)
  • 单个表的固定开销更优 (测试 2 < 测试 3,数据量小时)

结论:

避免稀疏数组 :索引不连续会导致大量 nil 值占用内存

合理设计表结构 :优先使用连续数组,减少混合键类型的使用

根据数据量选择方案 :小数据量用单表,大数据量考虑分离表

8.2 内存使用检查清单

在实际开发中,遵循以下检查清单可帮助避免常见的内存问题:

  • 变量作用域控制:所有变量都使用 local 声明,除非确实需要在不同作用域间共享数据
  • 及时清理大对象:大对象使用完毕后立即将其引用设为 nil,特别是作为临时缓冲的大表或长字符串
  • 循环内优化:避免在循环内部频繁创建临时对象,可将对象创建移到循环外部并复用
  • 字符串拼接优化:大量字符串拼接时,使用 table.concat 替代 .. 运算符,减少中间字符串的创建
  • 大文件处理:处理大文件时采用分块读取或流式处理,避免一次性加载整个文件到内存
  • 内存监控:定期检查内存使用峰值和增长趋势,定期通过 collectgarbage("collect") 清除内存
  • 垃圾回收调优:根据应用场景调整垃圾回收参数(如 collectgarbage("setpause") 和 collectgarbage("setstepmul")),平衡性能和内存使用
搜索