内存管理的意义

内存是计算机系统中重要的基本资源之一。内存管理是指对其分配、使用和回收的管理。在硬件层面,内存管理涉及对 RAM 等数据存储硬件的管理;在操作系统和应用层面,则是保障各个程序内存的正常分配和回收。PHP 7 的内存管理是在 C 的内存函数库之上做了一层封装,本章将重点讨论在 PHP 7 中如何管理 PHP 程序自身运行时的对象和数据结构所用的内存。计算机系统的运行无时无刻不依赖内存,所以内存管理是否安全、高效也严重影响着系统性能的表现。如前文所述,既然操作系统已经提供了一套内存管理的函数,那为什么 PHP 7 还要自己实现一套内存管理方案呢?首先,PHP 7 的使用者不需要像 C/C++ 那样手动申请和释放内存。在开发者对内存随用随取(定义对象、数组等)的背后,是 PHP 7 内核的内存管理提供的支撑,让开发者可以专注于于业务逻辑,而不用关心内存的申请和释放,大大提高了业务支撑的效率。

其次,向操作系统申请内存以及释放内存、回收内存,会产生用户态和内核态的切换,是高耗时的操作。PHP 7 内存管理器充当了应用层和操作系统内核的中间人,大大减少应用直接向内核频繁申请小块内存的操作,同时 PHP 7 内存管理器会择时释放,提升系统的整体性能。

另外很重要的一点是,PHP 7 内存管理还会减少内存 “碎片化” 问题。没有内存管理器,如果 PHP 程序持续运行、反复申请与释放内存导致连续内存产生大量碎片,会使得内存利用率降低;内存管理器的内存池技术能按块大小分级分配和回收,减少碎片化。图9-1是 PHP 7 内存管理器示意图。

image 2024 06 10 12 18 38 597
Figure 1. 图9-1 PHP 7内存管理器示意图

如图9-1所示,PHP 脚本运行所需的内存空间不是直接从系统申请,而是调用 Zend Memory Manager(Zend 内存管理器,以下简称 MM)提供的一系列接口函数(如 zend_mm_alloc_small)申请:如果 MM 中的可用内存够用,直接分配给 PHP 程序;如果 MM 中的可用内存不够,MM 再从系统申请。可见这样能有效减少系统调用的次数,并优化内存空间的使用效率。

一般都认为 C\C++ 开发要难于 PHP,很大程度上,难度在 于内存管理这一块。C\C++ 开发时,要自己管理动态内存,自己申请,自己释放,申请了却没有释放,会造成内存泄漏,不断浪费内存以致拖慢系统;内存使用越界,会让程序崩溃。虽然 C++ 中的 STL 库和 BOOST 库提供了多款智能指针用于管理内存,但是不同的智能指针有不同的应用场景,PHP 的内存管理把工程师从内存管理的梦魇中解救出来。

默认的系统分配和释放内存算法自然也考虑了性能,然而,为了应付更复杂、更广泛的情况,这些内存管理算法的通用版本需要做更多的额外工作。而对于某一个具体的应用程序来说,适合自身特定的内存分配释放模式的自定义内存则可以获得更好的性能。

所以我们需要内存池技术,当申请者第一次申请内存时,直接申请一块大块内存(通常是一页),将此次申请需要的内存部分返回给申请者,并将剩下的内部放到池子中,以后申请者再申请内存时,直接在剩下的部分中选取合适的大小返回给申请者。

我们来看下维基百科对内存池的定义:内存池提供了一个更有效率的解决方案,即预先规划一定数量的内存区块,使得整个程序可以在运行期规划(allocate)、使用(access)、归还(free)内存区块。

内存池不仅在用户态应用程序中广泛使用,同时在 Linux 内核也广泛使用,在内核中有不少地方内存分配不允许失败。所以在这种情况下,为了确保内存能够成功分配,内核开发者创建了一个内存池,只分配给此情况使用,此方法虽然浪费了内存,但是可以从根本上保证系统更加稳定。

从应用角度来看这个问题,内存管理兼顾性能、灵活与安全性,对 PHP 的扩展性起到了很好的支撑作用。诸如 PHP 7 的重要数据结构——数组和字符串,为扩展开发者提供了便捷的高级变量类型。而在不同生命周期中,这些变量有持久化和非持久化的不同需求。PHP 7 的内存管理为不同场景设计了不同的 API,开发者可以将更多精力投入上层逻辑,尽量避开 “内存泥沼”。