在 PHP 7.4 中,WeakReference 与 WeakMap 的引入标志着语言对对象生命周期管理的一次重要进化。它们并非语法糖,而是直接触及 Zend 引擎引用计数与垃圾回收机制的核心。理解其内核实现,不仅能帮助开发者写出更健壮的长生命周期应用,还能避免因循环引用导致的内存泄漏。
为什么需要弱引用
在传统 PHP 引用模型中,$a = $obj 会使对象的引用计数加一。只要该引用存在,对象就不会被销毁。这在实现缓存、观察者模式或对象映射时会造成问题:缓存持有对象引用,导致对象无法被回收,即使外部已无任何使用。
弱引用允许你“观察”一个对象而不增加其引用计数。当对象仅被弱引用指向时,Zend 引擎会正常销毁它,并自动将所有指向它的弱引用置为 null。
WeakReference 的内核结构
WeakReference 在内核中对应 zend_weakref 结构体:
typedef struct _zend_weakref {
zend_object std;
zend_object *obj; /* 指向被弱引用的对象 */
zend_weakref *next; /* 同一对象上的弱引用链表 */
zend_weakref **prev;
} zend_weakref;
关键点在于 obj 字段:它指向目标对象,但不增加该对象的引用计数。同时,每个对象(zend_object)内部维护一个 weakrefs 链表头。当创建 WeakReference 时,引擎将新弱引用插入目标对象的 weakrefs 链表。
当对象被销毁时,zend_objects_store_del 会遍历该对象的 weakrefs 链表,将每个弱引用的 obj 置为 NULL。此后调用 WeakReference::get() 返回 null。
这种设计使得弱引用的创建与销毁都是 O(1) 操作,且对象销毁时清理弱引用的开销与弱引用数量成正比。
WeakMap:键为对象的弱引用映射
WeakMap 是更高级的抽象。它允许以对象为键存储数据,且不阻止键对象被回收。当键对象被销毁时,对应的键值对自动从 Map 中移除。
内核中 WeakMap 基于 zend_weakmap 结构:
typedef struct _zend_weakmap {
zend_object std;
HashTable ht; /* 存储映射关系 */
zend_weakref *weakrefs; /* 所有键对象的弱引用集合 */
} zend_weakmap;
WeakMap 的 ht 是一个普通哈希表,但键的存储方式特殊:它不直接存储对象指针作为键,而是为每个键对象创建一个内部弱引用,并将该弱引用的句柄作为哈希键。同时,WeakMap 会在每个键对象上注册一个回调,当对象销毁时触发 zend_weakmap_remove_key,从 ht 中删除对应条目。
这种实现保证了:
- 键对象存活时,映射有效;
- 键对象销毁时,映射条目自动清理,无需手动干预;
- 遍历
WeakMap时不会遇到已销毁的键。
与垃圾回收的交互
PHP 的垃圾回收器(GC)负责处理循环引用。弱引用不增加引用计数,因此不会阻止 GC 回收循环引用对象。但有一个微妙之处:当 GC 扫描到某个对象是循环引用的一部分时,它会先调用对象的析构函数,然后释放对象。在释放前,GC 会触发弱引用清理,确保所有弱引用被置空。
这意味着在析构函数中访问 WeakReference::get() 可能返回 null,因为弱引用清理发生在析构之后、对象内存释放之前。开发者不应依赖析构函数中弱引用的有效性。
性能与内存考量
弱引用的内存开销很小:每个 WeakReference 对象本身约 56 字节(64 位系统),加上目标对象上的链表指针。WeakMap 的每个条目额外维护一个弱引用句柄,但避免了为每个键单独创建 WeakReference 对象。
在性能敏感场景中,WeakMap 通常优于手动维护 WeakReference 数组,因为其哈希查找和自动清理都在 C 层完成,无需 PHP 用户态循环。
典型应用场景
- 对象缓存:缓存持有弱引用,当外部不再使用对象时自动释放。
- 观察者模式:观察者列表使用
WeakMap存储,避免观察者无法被回收。 - 元数据附加:为对象附加额外数据而不修改对象本身,且不延长对象生命周期。
- 循环引用打破:在父子互引结构中,父对子的引用可用弱引用,避免内存泄漏。
注意事项
WeakReference和WeakMap不能序列化。WeakMap的键必须是对象,且不能是null。- 弱引用不保证对象立即销毁,仅保证不阻止销毁。
- 在
WeakMap上调用count()返回当前有效键的数量。
总结
WeakReference 与 WeakMap 的内核实现围绕一个核心原则:观察但不拥有。通过将弱引用挂载到目标对象的链表,并在对象销毁时统一清理,Zend 引擎以极低的开销实现了安全的弱引用语义。理解这些机制,有助于在架构设计中更精准地控制对象生命周期,写出内存效率更高的 PHP 应用。
未经允许不得转载:任鹏个人博客 » PHP 弱引用与对象生命周期:WeakReference 与 WeakMap 的内核实现

