Elixir/Erlang函数调用传参的变量内存管理机制是怎样的
Elixir/Erlang 函数调用的变量内存管理与传参机制
Elixir 编译后完全运行在 BEAM 虚拟机上,和 Erlang 共享完全一致的内存管理与传参逻辑,不存在语言层面的实现差异。
基础内存规则
- BEAM 中所有运行时值统称为 term,所有 term 天生不可变,创建后不存在任何原地修改的可能。
- 每个进程持有完全独立的私有栈、私有堆,不同进程默认不能直接访问对方内存空间里的内容。
- term 按存储方式分为三类:
- 即时值:小整数、原子、nil、本地pid这类体积不超过一个机器字长的值,直接存在寄存器、栈槽里,不需要额外分配堆内存
- 复合值:列表、元组、普通映射、小二进制(长度小于64字节)这类结构,存储在所属进程的私有堆上
- 大二进制:长度大于等于64字节的二进制,统一存在全局共享堆上,靠引用计数管理生命周期
同进程函数调用的传参逻辑
同进程内的函数调用是BEAM里最高频的操作,传参全程不会做任何深拷贝:
- 即时值直接复制本身的值,传入被调用函数的参数寄存器或对应栈帧位置,开销是固定常数级
- 存在私有堆上的复合值,传参时仅复制指向对应堆内存的引用(本质是指针),不会递归拷贝整个结构,哪怕是百万级长度的列表、GB级的元组,传参开销也是O(1)
- 全局堆上的大二进制,传参时仅复制引用,同时给对应二进制的引用计数+1
这种实现不会产生传引用常见的副作用问题:因为所有term不可变,被调用函数拿到引用后,根本无法修改原term的内容,任何对参数的“修改”操作(比如给列表追加元素、更新映射键值)都会在堆上创建新的term,完全不会影响调用方持有的原始值。从开发者的语义感知上,这种行为和纯传值完全一致。
跨进程传参的特殊逻辑
如果参数是通过消息发送传递给其他进程(跨进程的函数调用本质是消息交互),规则会有区别:
- 即时值直接复制值传递,和同进程逻辑一致
- 私有堆上的复合值会做完整的深拷贝,复制到接收进程的私有堆上,拷贝完成后两个进程持有的是完全独立的两份值,互不影响
- 大二进制仍然只传递引用,引用计数+1,不做拷贝,不可变特性从根源上避免了并发修改问题
传参类型的术语界定
BEAM的传参既不是教科书定义的纯传值,也不是纯传引用:
- 从底层实现看,大值传参没有做全量拷贝,仅传递引用,不符合传统传值“传参时完整拷贝参数”的特征
- 从语义表现看,不存在传引用特有的“函数内修改参数影响外部变量”的副作用,完全符合传值的行为预期
- 行业内通常把BEAM的这种实现称为共享传参(call by sharing),是函数式语言依托不可变特性做的性能优化,在保证纯传值语义的前提下,避免了大值传参的拷贝开销。
内存回收逻辑
- 进程私有堆上的term,当栈和寄存器中没有任何指向它的引用时,会在该进程下次触发GC时被回收。BEAM的GC是进程级独立运行的,不会阻塞其他进程执行。
- 全局堆上的大二进制靠引用计数回收,当所有进程持有的对应引用都被释放时,内存会被直接回收,不需要等待GC扫描。
内容的提问来源于stack exchange,提问作者Aadarsh Bishen
相关产品推荐
相关产品推荐

