当程序创建列表或用户定义新函数(例如加载库)时,相关数据会存放在普通存储中。 若普通存储不足,Emacs 会向操作系统申请更多内存。 不同类型的 Lisp 对象(如符号、cons 单元、小型向量、标记等) 会被分隔存放在不同的内存块中。 (大型向量、长字符串、缓冲区及其他较大的编辑类型对象会单独分配内存块, 每个对象占用一块;短字符串会打包存入 8KB 块,小型向量则打包存入 4KB 块。)
除基础向量外,标记、覆盖层、缓冲区等许多对象都以类似向量的方式管理。
对应的 C 语言数据结构包含 union vectorlike_header 字段,
其 size 成员存储由 enum pvec_type 枚举的子类型,
同时记录该结构包含多少个 Lisp_Object 字段以及剩余数据大小。
该信息用于计算对象的内存占用量,并在向量分配代码遍历向量块时使用。
内存使用一段时间后被释放是很常见的情况, 例如关闭缓冲区或删除指向对象的最后一个指针。 Emacs 提供 垃圾回收器(garbage collector) 以回收这些废弃的存储空间。 垃圾回收器的核心工作方式是找到并标记所有仍可被 Lisp 程序访问的 Lisp 对象。 首先,它假定所有符号、其值与关联函数定义,以及当前栈上的所有数据均可访问。 所有可通过其他可访问对象间接到达的对象同样被视为可访问, 但该计算采用“保守”策略,因此可能略微高估可访问对象的数量。
标记完成后,所有未标记对象即为垃圾。 无论 Lisp 程序或用户执行何种操作,都无法再引用这些对象, 因为已不存在访问它们的路径。 它们占用的空间可以被安全复用,因为不会再被使用。 垃圾回收的第二阶段(清除阶段)会安排复用这些空间。 (但由于标记采用保守策略,并非所有未使用对象都能在单次清除中被保证回收。)
清除阶段会将未使用的 cons 单元加入 空闲链表(free list) 以备后续分配, 符号与标记对象同理。它会压缩可访问字符串,减少其占用的 8KB 块数量, 随后释放其余 8KB 块。向量块中不可访问的向量会被合并, 形成尽可能大的空闲区域;若某空闲区域占满整个 4KB 块,则直接释放该块。 否则,该空闲区域会被记录到空闲链表数组中, 数组中每个条目对应相同大小区域的空闲链表。 大型向量、缓冲区及其他大型对象会单独分配与释放。
Common Lisp 说明: 与其他 Lisp 不同,GNU Emacs Lisp 不会在空闲链表为空时调用垃圾回收器。 相反,它会直接向操作系统申请更多存储空间, 并继续执行直到已使用
gc-cons-threshold字节的内存。这意味着你可以在 Lisp 程序某段代码前显式调用垃圾回收, 确保该段代码执行期间不会触发回收 (前提是该段代码不会占用过多内存而被迫触发第二次回收)。
该命令执行一次垃圾回收,并返回当前内存使用信息。
(若自上次垃圾回收后已使用超过 gc-cons-threshold 字节的 Lisp 数据,
垃圾回收也可能自动触发。)
garbage-collect 返回一个列表,描述各类型内存使用情况,
每条记录格式为 ‘(name size used)’
或 ‘(name size used free)’。
其中 name 为表示对象类型的符号,
size 为单个对象占用字节数,
used 为堆中存活对象数量,
可选的 free 为 Emacs 保留以备后续分配的非存活对象数量。
整体返回结果格式如下:
((consescons-size used-conses free-conses) (symbolssymbol-size used-symbols free-symbols) (stringsstring-size used-strings free-strings) (string-bytesbyte-size used-bytes) (vectorsvector-size used-vectors) (vector-slotsslot-size used-slots free-slots) (floatsfloat-size used-floats free-floats) (intervalsinterval-size used-intervals free-intervals) (buffersbuffer-size used-buffers) (heapunit-size total-size free-size))
示例如下:
(garbage-collect)
⇒ ((conses 16 49126 8058) (symbols 48 14607 0)
(strings 32 2942 2607)
(string-bytes 1 78607) (vectors 16 7247)
(vector-slots 8 341609 29474) (floats 8 71 102)
(intervals 56 27 26) (buffers 944 8)
(heap 1024 11715 2678))
下表为各字段说明。注意最后一项 heap 为可选字段,
仅当底层 malloc 实现提供 mallinfo 函数时才会出现。
cons 单元的内部大小,即 sizeof (struct Lisp_Cons)。
正在使用的 cons 单元数量。
已从操作系统分配但当前未使用的 cons 单元数量。
符号的内部大小,即 sizeof (struct Lisp_Symbol)。
正在使用的符号数量。
已从操作系统分配但当前未使用的符号数量。
字符串头部的内部大小,即 sizeof (struct Lisp_String)。
正在使用的字符串头部数量。
已从操作系统分配但当前未使用的字符串头部数量。
为方便使用而设,等于 sizeof (char)。
所有字符串数据的总字节大小。
长度为 1 的向量(包含头部)的字节大小。
从向量块中分配的向量头部数量。
向量槽位的内部大小,始终等于 sizeof (Lisp_Object)。
所有已使用向量中的槽位总数。 根据平台不同,槽位计数可能包含部分或全部向量头部开销。
所有向量块中的空闲槽位数量。
浮点对象的内部大小,即 sizeof (struct Lisp_Float)。
(请勿与平台原生的 float 或 double 混淆。)
正在使用的浮点数数量。
已从操作系统分配但当前未使用的浮点数数量。
区间对象的内部大小,即 sizeof (struct interval)。
正在使用的区间数量。
已从操作系统分配但当前未使用的区间数量。
缓冲区的内部大小,即 sizeof (struct buffer)。
(请勿与 buffer-size 函数返回值混淆。)
正在使用的缓冲区对象数量。
包含对用户不可见的已关闭缓冲区,
即 all_buffers 列表中的所有缓冲区。
堆空间计量单位,始终为 1024 字节。
堆总大小,以 unit-size 为单位。
当前未使用的堆空间,以 unit-size 为单位。
若纯净空间发生溢出 (see 纯净存储),
且 Emacs 由现已废弃的 unexec 方式转储生成
(see 构建 Emacs),则 garbage-collect 返回 nil,
因为该场景下无法执行真正的垃圾回收。
若该变量非 nil,Emacs 会在垃圾回收开始与结束时显示提示信息。
默认值为 nil。
这是垃圾回收结束时运行的普通钩子。 钩子函数运行期间垃圾回收会被禁止,编写时需格外注意。
该变量的值表示一次垃圾回收后,需为 Lisp 对象分配多少字节存储
才会触发下一次垃圾回收。
你可以使用 garbage-collect 返回的结果查看特定对象类型的大小;
分配给缓冲区内容的空间不计入该阈值。
初始阈值为 GC_DEFAULT_THRESHOLD,定义于 alloc.c。
由于其以 word_size 为单位定义,
默认 32 位配置下值为 400000,
64 位与启用 --with-wide-int 选项的 32 位编译版本为 800000。
若设置更大的值,垃圾回收频率会降低。
这会减少垃圾回收耗时(使 Lisp 程序在回收间隔内运行更快),
但会增加总内存占用。
运行会创建大量 Lisp 数据的程序时可适当调大,尤其需要提升运行速度时。
但我们不建议长期增大该阈值,且切勿设置超过程序合理运行所需的大小。
使用超出必要的阈值可能导致系统级内存压力升高,
并使单次垃圾回收耗时大幅增加,因此应避免。
你可以设置更小的值提高回收频率,最低可设为 GC_DEFAULT_THRESHOLD 的十分之一。
低于该最小值的设置仅生效至下次垃圾回收,
届时 garbage-collect 会将阈值恢复为最小值。
该变量的值以当前堆大小的比例形式,
指定触发垃圾回收前允许的内存分配量。
该条件与 gc-cons-threshold 同时生效,
仅当两者均满足时才会触发垃圾回收。
堆大小增大时,垃圾回收耗时也会增加。 因此按比例降低回收频率是合理的。
交互会话与 Emacs 转储时 (see 构建 Emacs), 初始比例为 0.1;非交互(批处理)会话中为 1.0。
与 gc-cons-threshold 相同,请勿非必要地增大该值,
且切勿长期保持较大值。
通过 gc-cons-threshold 与 gc-cons-percentage
控制垃圾回收仅为近似控制。
尽管 Emacs 会定期检查阈值是否耗尽,
但出于效率考虑,不会在堆或阈值每次变化后立即检查,
因此阈值耗尽不会立即触发垃圾回收。
此外,为提高阈值计算效率,Emacs 会对堆大小进行近似计算,
仅统计堆中当前可访问对象占用的字节数。
garbage-collect 返回的值按数据类型细分 Lisp 数据的内存占用。
与之相对,函数 memory-limit 提供 Emacs 当前使用的总内存信息。
该函数返回 Emacs 当前使用的虚拟内存总字节数的估计值并除以 1024。 可用于大致了解操作对内存占用的影响。
若 Emacs 即将耗尽 Lisp 对象内存,该变量为 t,否则为 nil。
返回一组数字列表,统计当前 Emacs 会话中创建的各类对象数量。 每种计数器对应一类对象的创建次数。 详情参见文档字符串。
该函数返回系统总内存与空闲内存大小。
在不支持的系统上返回值可能为 nil。
若 default-directory 指向远程主机,
则返回该主机的内存信息。
该变量记录当前 Emacs 会话中已执行的垃圾回收总次数。
该变量以浮点数形式记录当前 Emacs 会话中垃圾回收累计耗时(秒)。
查看 Emacs 内存分布(各类变量、缓冲区、缓存)有时十分有用。 该命令会打开一个新缓冲区(名为 ‘"*Memory Report*"’), 提供内存使用概览,并列出占用 “最大” 的缓冲区与变量。
此处所有数据均为近似值,因为不存在统一的变量大小计算方式。 例如两个变量可能共享同一数据结构的部分内容, 此时会被重复计算,但该命令仍能有效概览 Emacs 各模块的内存使用情况。