内存是计算机非常关键的部件之一,是暂时存储程序以及数据的空间,CPU只有有限的寄存器可以用于存储计算数据,而大部分的数据都是存储在内存中的,程序运行都是在内存中进行的。和CPU计算能力一样, 内存也是决定计算效率的一个关键部分。
计算中的资源中主要包含:CPU计算能力,内存资源以及I/O。现代计算机为了充分利用资源, 而出现了多任务操作系统,通过进程调度来共享CPU计算资源,通过虚拟存储来分享内存存储能力。 本章的内存管理中不会介绍操作系统级别的虚拟存储技术,而是关注在应用层面: 如何高效的利用有限的内存资源。
目前除了使用C/C++等这类的低层编程语言以外,很多编程语言都将内存管理移到了语言之后, 例如Java, 各种脚本语言:PHP/Python/Ruby等等,程序手动维护内存的成本非常大, 而这些脚本语言或新型语言都专注于特定领域,这样能将程序员从内存管理中解放出来专注于业务的实现。 虽然程序员不需要手动维护内存,而在程序运行过程中内存的使用还是要进行管理的, 内存管理的工作也就编程语言实现程序员的工作了。
内存管理的主要工作是尽可能高效的利用内存。
内存的使用操作包括申请内存,销毁内存,修改内存的大小等。 如果申请了内存在使用完后没有及时释放则可能会造成内存泄露,如果这种情况出现在常驻程序中, 久而久之,程序会把机器的内存耗光。所以对于类似于PHP这样没有低层内存管理的语言来说, 内存管理是其至关重要的一个模块,它在很大程序上决定了程序的执行效率,
在PHP层面来看,定义的变量、类、函数等等实体在运行过程中都会涉及到内存的申请和释放, 例如变量可能会在超出作用域后会进行销毁,在计算过程中会产生的临时数据等都会有内存操作, 像类对象,函数定义等数据则会在请求结束之后才会被释放。在这过程中合适申请内存合适释放内存就比较关键了。 PHP从开始就有一套属于自己的内存管理机制,在5.3之前使用的是经典的引用计数技术, 但引用技术存在一定的技术缺陷,在PHP5.3之后,引入了新的垃圾回收机制,至此,PHP的内存管理机制更加完善。
从某个意义上讲,资源总是有限的,计算机资源也是如此,衡量一个计算机处理能里的指标有很多, 同时也根据不同的应用需要会有不同的指标,比如3D游戏对显卡就有些要求,而Web服务器对吞吐量及响应时间有要求, 通常CPU、内存及硬盘的读取和计算速度具有决定性的作用,在同一时刻这些资源是有限的, 真是因为有限我们才需要合理的利用他们。
操作系统的内存管理
当计算机的电源被打开之后,不管你使用的是什么操作系统,这些软件可能已经在使用内存了。 这是由计算机的结构决定的,操作系统也是一种软件,只不过它是比较特殊的软件, 管理计算机的所有资源,普通应用程序和操作系统的关系有点像老师和学生,老师通常管理一切, 而学生的行为会收到老师或学校规定的限制,例如应用程序无法直接访问物理内存或者其他硬件资源。
操作系统直接管理着内存,所以操作系统也需要进行内存管理,内存管理是如此之重要, 计算机中通常都有内存管理单元(MMU) 用于处理CPU对内存的访问。
应用层的内存管理
由于计算机的内存由操作系统进行管理,所以普通应用程序是无法直接对内存进行访问的, 应用程序只能向操作系统申请内存,通常的应用也是这么做的,在需要的时候通过类似malloc之类的库函数 想操作系统申请内存,在一些对性能要求较高的应用场景下是需要频繁的使用和释放内存的, 比如Web服务器,编程语言等,由于向操作系统申请内存空间会引发系统调用, 系统调用和普通的应用层函数调用差别非常大,因为系统调用会将CPU从用户态切换到内核, 因为涉及到物理内存的操作,只有操作系统才能进行,而这种切换的成本是非常大的, 如果频繁的在内核态和用户态之间切换会产生性能问题。
鉴于系统调用的开销,一些对性能有要求的应用通常会自己在用户态进行内存管理, 例如第一次申请稍大的内存留着备用,而使用完释放的内存并不是马上归还给操作系统, 比如可以将内存进行复用,这样也可以避免多次的内存申请和释放。
PHP不需要显式的对内存进行管理,这些工作都由PHP解释器进行了。由此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");
?>
既然我们能动态的调整最大的内存占用,那我们是否有办法获取目前的内存占用情况呢?答案是肯定的。
memory_get_usage(),这个函数的作用是获取 目前PHP脚本所用的内存大小。
memory_get_peak_usage(),这个函数的作用返回 当前脚本到目前位置所占用的内存峰值,这样就可能获取到目前的脚本的内存需求情况。
单就PHP用户空间提供的功能来说,我们似乎无法控制内存的使用,只能被动的获取内存的占用情况, 这样的话我们学习内存管理有什么用呢?
前面的章节有介绍到引用计数,函数表,符号表,常量表等。当我们明白这些信息都会占用内存的时候, 我们可以有意的避免不必要的浪费内存,比如我们在项目中通常会使用autoload来避免一次性把不一定会使用的类 包含进来,而这些信息是会占用内存的,如果我们及时把不再使用的变量unset掉之后可能会释放掉它所占用的空间,