深入理解PHP内核

Thinking In PHP Internal

第一节 内存管理概述

从某个意义上讲,资源总是有限的,计算机资源也是如此,衡量一个计算机处理能力的指标有很多, 根据不同的应用需要也会有不同的指标,比如3D游戏对显卡的性能有要求,而Web服务器对吞吐量及响应时间有要求, 通常CPU、内存及硬盘的读取和计算速度具有决定性的作用,在同一时刻这些资源是有限的, 正是因为有限我们才需要合理的利用他们。

操作系统的内存管理

当计算机的电源被打开之后,不管你使用的是什么操作系统,这些软件可能已经在使用内存了。 这是由计算机的结构决定的,操作系统也是一种软件,只不过它是比较特殊的软件, 管理计算机的所有资源,普通应用程序和操作系统的关系有点像老师和学生,老师通常管理一切, 而学生的行为会受到老师或学校规定的限制,例如普通应用程序无法直接访问物理内存或者其他硬件资源。

操作系统直接管理着内存,所以操作系统也需要进行内存管理,内存管理是如此之重要, 计算机中通常都有内存管理单元(MMU) 用于处理CPU对内存的访问。

应用层的内存管理

由于计算机的内存由操作系统进行管理,所以普通应用程序是无法直接对内存进行访问的, 应用程序只能向操作系统申请内存,通常的应用也是这么做的,在需要的时候通过类似malloc之类的库函数 向操作系统申请内存,在一些对性能要求较高的应用场景下是需要频繁的使用和释放内存的, 比如Web服务器,编程语言等,由于向操作系统申请内存空间会引发系统调用, 系统调用和普通的应用层函数调用性能差别非常大,因为系统调用会将CPU从用户态切换到内核, 因为涉及到物理内存的操作,只有操作系统才能进行,而这种切换的成本是非常大的, 如果频繁的在内核态和用户态之间切换会产生性能问题。

鉴于系统调用的开销,一些对性能有要求的应用通常会自己在用户态进行内存管理, 例如第一次申请稍大的内存留着备用,而使用完释放的内存并不是马上归还给操作系统, 可以将内存进行复用,这样可以避免多次的内存申请和释放所带来的性能消耗。

PHP不需要显式的对内存进行管理,这些工作都由Zend引擎进行管理了。PHP内部有一个内存管理体系, 它会自动将不再使用的内存垃圾进行释放,这部分的内容后面的小节会介绍到。

PHP中内存相关的功能特性

可能有很多的读者碰到过类似下面的错误吧:

Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)

这个错误的信息很明确,PHP已经达到了允许使用的最大内存了,通常上来说这很有可能是我们的程序编写的有些问题。 比如:一次性读取超大的文件到内存中,或者出现超大的数组,或者在大循环中的没有及时释放掉不再使用的变量, 这些都有可能会造成内存占用过大而被终止。

PHP默认的最大内存使用大小是32M, 如果你真的需要使用超过32M的内存可以修改php.ini配置文件的如下配置:

memory_limit = 32M

如果你无法修改php配置文件,如果你的PHP环境没有禁用ini_set()函数,也可以动态的修改最大的内存占用大小:

<?php
ini_set("memory_limit", "128M");

既然我们能动态的调整最大的内存占用,那我们是否有办法获取目前的内存占用情况呢?答案是肯定的。

  1. memory_get_usage(),这个函数的作用是获取 目前PHP脚本所用的内存大小。
  2. memory_get_peak_usage(),这个函数的作用返回 当前脚本到目前位置所占用的内存峰值,这样就可能获取到目前的脚本的内存需求情况。

单就PHP用户空间提供的功能来说,我们似乎无法控制内存的使用,只能被动的获取内存的占用情况, 这样的话我们学习内存管理有什么用呢?

前面的章节有介绍到引用计数,函数表,符号表,常量表等。当我们明白这些信息都会占用内存的时候, 我们可以有意的避免不必要的浪费内存,比如我们在项目中通常会使用autoload来避免一次性把不一定会使用的类 包含进来,而这些信息是会占用内存的,如果我们及时把不再使用的变量unset掉之后可能会释放掉它所占用的空间,

前面之所以会说把变量unset掉时候可能会把它释放掉的原因是: 在PHP中为了避免不必要的内存复制,采用了引用计数和写时复制的技术, 所以这里unset只是将引用关系打破,如果还有其他变量指向该内存, 它所占用的内存还是不会被释放的。
当然这还有一种情况:出现循环引用,这个就得靠gc来处理了, 内存不会当时就释放,只有在gc环节才会被释放。

后面的章节主要介绍PHP在运行时的内存使用和管理细节。这也能帮助我们写出更为内存友好的PHP代码。