防止有内存泄漏并尽可能快地释放所有内存是内存管理的重要组成部分 长时间运行进程的内存泄漏
http://php.net/manual/en/internals2.memory.management.php
Basic memory management
The engine's memory management is implemented with features important to a system like PHP. The exact functionality of the engine's memory management and the optimizations performed are out of the scope of this document. However, a good understanding of its functionality provides a basis for a good understanding of the rest of the Hacker's Guide
, and introduce you to terminology and functionality used throughout PHP.
The most important of its features for the Hacker
, and the first thing to mention is tracking allocations. Tracking allocations allow the memory manager to avoid leaks, a thorn in the side of most Hackers
. When PHP is built in debug mode (--enable-debug
), detected leaks are reported, in a perfect world they would never get to deployment.
While tracking allocations is an important, and highly useful feature, the Hacker
should not become lazy! Always attempt to resolve leaks before deploying your code, a memory leak in a SAPI environment can become a very big problem, very quickly.
Another, perhaps more incidental but still noteworthy, feature is that the memory manager is the part that allows a hard limit on memory usage for each instance of PHP. As we all know, there is no such thing as unlimited. If some code is running out of memory, it is likely to be written wrong, either by the Hacker
, or the programmer of PHP. Limiting the memory therefore is not a restriction on the language that is supposed to be experienced in production, it is simply a way from stopping development environments from spiraling out of control when mistakes are made, and equally, when bugs are found in production.
From the Hacker's
perspective, the memory management API looks very much like libc's (or whoever the Hacker
prefers !) malloc implementation.
Prototype | Description |
---|---|
void *emalloc(size_t size) |
Allocate size bytes of memory. |
void *ecalloc(size_t nmemb, size_t size) |
Allocate a buffer for nmemb elements of size bytes and makes sure it is initialized with zeros. |
void *erealloc(void *ptr, size_t size) |
Resize the buffer ptr , which was allocated using emalloc to hold size bytes of memory. |
void efree(void *ptr) |
Free the buffer pointed by ptr . The buffer had to be allocated by emalloc . |
void *safe_emalloc(size_t nmemb, size_t size, size_t offset) |
Allocate a buffer for holding nmemb blocks of each size bytes and an additional offset bytes. This is similar to emalloc(nmemb * size + offset) but adds a special protection against overflows. |
char *estrdup(const char *s) |
Allocate a buffer that can hold the NULL-terminated string s and copy the s into that buffer. |
char *estrndup(const char *s, unsigned int length) |
Similar to estrdup while the length of the NULL-terminated string is already known. |
Note: The engines memory management functions do not return
NULL
upon failure, if memory cannot be allocated at runtime, the engine bails and raises an error.
Always use valgrind before deploying code and as a normal part of the Hacker's
process. The engine can only report and detect leaks where it has allocated the memory. All of PHP is only a thin wrapper around third parties, those third parties do not use the engines memory management. Additionally, valgrind will catch errors that do not always halt or even have an apparent effect at execution time, it is just as important that there should be no errors, as it is important that avoidable leaks should be avoided.
Note: Some leaks are unavoidable, some libraries rely on the end of a process to free some of their structures, this is normal under some circumstances and acceptable where it is out of the
Hacker's
control.
While executing in a debug environment, configured with --enable-debug
, the leak function used in the next example is actually implemented by the engine and is available to call in userland.
Example #1 Leak Detection in Action
ZEND_FUNCTION(leak) { long leakbytes = 3; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &leakbytes) == FAILURE) { return; } emalloc(leakbytes); }
The above example will output something similar to:
[Thu Oct 22 02:14:57 2009] Script: '-' /home/johannes/src/PHP_5_3/Zend/zend_builtin_functions.c(1377) : Freeing 0x088888D4 (3 bytes), script=- === Total 1 memory leaks detected ===
Note: USE_ZEND_ALLOC=0 in the environment will stop the memory manager from functioning, all allocations fall back on the default system allocators which can be useful for debugging leaks, and should only be used for this purpose.
内存管理基础
用 C 语言编程时,开发者要手工地进行内存管理。因为 PHP 经常用作 Web 服务器的模块,内存管理与预防内存泄漏紧密关联。此外要知道 PHP 可能用于线程环境中,这意味着全局变量可能导致竞争状况。有关线程内全局数据处理的信息请参见作为线程隔离设施的 线程安全的资源管理器。
此外,Zend 引擎要面对一个十分特殊的使用模式:在一段比较短的时间内,许多 zval 结构大小的内存块和其他的小内存块被申请又再被释放。PHP 的内存管理也很重视 memory_limit(内存限制)。
为了满足以上的需求,Zend 引擎提供为了处理请求相关数据提供了一种特殊的内存管理器。请求相关数据是指只需要服务于单个请求,最迟会在请求结束时释放的数据。扩展开发者主要接触下表中列出的惯例。虽然一些所提供的便捷功能是用宏实现的,但在本文中会象函数一样对待。
原型 | 说明 |
---|---|
void *emalloc(size_t size) |
分配 size 字节的内存。 |
void *ecalloc(size_t nmemb, size_t size) |
给 nmemb 元素分配 size 字节的缓冲区并初始化为零。 |
void *erealloc(void *ptr, size_t size) |
修改使用 emalloc 分配的缓冲区 ptr 的大小为 size 字节。 |
void efree(void *ptr) |
释放 ptr 指向的缓冲区。缓冲区必须是由 emalloc 分配的。 |
void *safe_emalloc(size_t nmemb, size_t size, size_t offset) |
分配缓冲区来存放每块大小为 size 字节的 nmemb 块,并附加 offset 字节。类似于 emalloc(nmemb * size + offset) ,但增加了针对溢出的特殊保护。 |
char *estrdup(const char *s) |
分配一个可存放 NULL 结尾的字符串 s 的缓冲区,并将 s 复制到缓冲区内。 |
char *estrndup(const char *s, unsigned int length) |
类似于 estrdup ,但 NULL 结尾的字符串长度是已知的。 |
Note: 和与 C 标准库相似的部分不同,如果分配请求的内存出错,Zend 引擎的内存管理函数不会返回 NULL 值,而会跳出并中止当前请求。
如上所述,防止有内存泄漏并尽可能快地释放所有内存是内存管理的重要组成部分。因为安全原因,在请求结束时, Zend 引擎会释放所有由上面提到的 API 所分配的内存。如果 PHP 使用 --enable-debug
配置选项进行构建,这将产生一个警告。
Example #1 PHP 的泄漏报警
ZEND_FUNCTION(leak) { long leakbytes = 3; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &leakbytes) == FAILURE) { return; } emalloc(leakbytes); }
以上例程的输出类似于:
[Thu Oct 22 02:14:57 2009] Script: '-' /home/johannes/src/PHP_5_3/Zend/zend_builtin_functions.c(1377) : Freeing 0x088888D4 (3 bytes), script=- === Total 1 memory leaks detected ===
Note: 当使用 PHP 变量时,需要确认变量的内存要使用 emalloc 来分配,并注意引用计数。相关细节请查看 变量的使用。
Note: 内存泄漏检测仅可以发现由 emalloc 分配内存块导致的泄漏。为进行深层分析,建议使用内存检测器,如 valgrind 或 libumem 等。要简化此分析,可在 PHP 启动时通过设置环境变量 USE_ZEND_ALLOC=0 来禁用 PHP 的内存管理器。
长时间运行进程的内存泄漏
HTML_深入探讨PHP中的内存管理问题, 一、 内存 在PHP中, - phpStudy http://www.phpstudy.net/b.php/6586.html
一、 内存
在PHP中,填充一个字符串变量相当简单,这只需要一个语句""即可,并且该字符串能够被自由地修改、拷贝和移动。而在C语言中,尽管你能够编写例如"char *str = "hello world ";"这样的一个简单的静态字符串;但是,却不能修改该字符串,因为它生存于程序空间内。为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(例如strdup())来复制其内容。
以下为引用的内容: { char *str; str = strdup("hello world"); if (!str) { fprintf(stderr, "Unable to allocate memory!"); } } |
由于后面我们将分析的各种原因,传统型内存管理函数(例如malloc(),free(),strdup(),realloc(),calloc(),等等)几乎都不能直接为PHP源代码所使用。
二、 释放内存
在几乎所有的平台上,内存管理都是通过一种请求和释放模式实现的。首先,一个应用程序请求它下面的层(通常指"操作系统"):"我想使用一些内存空间"。如果存在可用的空间,操作系统就会把它提供给该程序并且打上一个标记以便不会再把这部分内存分配给其它程序。
当应用程序使用完这部分内存,它应该被返回到OS;这样以来,它就能够被继续分配给其它程序。如果该程序不返回这部分内存,那么OS无法知道是否这块内存不再使用并进而再分配给另一个进程。如果一个内存块没有释放,并且所有者应用程序丢失了它,那么,我们就说此应用程序"存在漏洞",因为这部分内存无法再为其它程序可用。
在一个典型的客户端应用程序中,较小的不太经常的内存泄漏有时能够为OS所"容忍",因为在这个进程稍后结束时该泄漏内存会被隐式返回到OS。这并没有什么,因为OS知道它把该内存分配给了哪个程序,并且它能够确信当该程序终止时不再需要该内存。
而对于长时间运行的服务器守护程序,包括象Apache这样的web服务器和扩展php模块来说,进程往往被设计为相当长时间一直运行。因为OS不能清理内存使用,所以,任何程序的泄漏-无论是多么小-都将导致重复操作并最终耗尽所有的系统资源。
现在,我们不妨考虑用户空间内的stristr()函数;为了使用大小写不敏感的搜索来查找一个字符串,它实际上创建了两个串的各自的一个小型副本,然后执行一个更传统型的大小写敏感的搜索来查找相对的偏移量。然而,在定位该字符串的偏移量之后,它不再使用这些小写版本的字符串。如果它不释放这些副本,那么,每一个使用stristr()的脚本在每次调用它时都将泄漏一些内存。最后,web服务器进程将拥有所有的系统内存,但却不能够使用它。
你可以理直气壮地说,理想的解决方案就是编写良好、干净的、一致的代码。这当然不错;但是,在一个象PHP解释器这样的环境中,这种观点仅对了一半。
三、 错误处理
为了实现"跳出"对用户空间脚本及其依赖的扩展函数的一个活动请求,需要使用一种方法来完全"跳出"一个活动请求。这是在Zend引擎内实现的:在一个请求的开始设置一个"跳出"地址,然后在任何die()或exit()调用或在遇到任何关键错误(E_ERROR)时执行一个longjmp()以跳转到该"跳出"地址。
尽管这个"跳出"进程能够简化程序执行的流程,但是,在绝大多数情况下,这会意味着将会跳过资源清除代码部分(例如free()调用)并最终导致出现内存漏洞。现在,让我们来考虑下面这个简化版本的处理函数调用的引擎代码:
以下为引用的内容: void call_function(const char *fname, int fname_len TSRMLS_DC){ zend_function *fe; char *lcase_fname; /* PHP函数名是大小写不敏感的, *为了简化在函数表中对它们的定位, *所有函数名都隐含地翻译为小写的 */ lcase_fname = estrndup(fname, fname_len); zend_str_tolower(lcase_fname, fname_len); if (zend_hash_find(EG(function_table),lcase_fname, fname_len + 1, (void **)&fe) == FAILURE) { zend_execute(fe->op_array TSRMLS_CC); } else { php_error_docref(NULL TSRMLS_CC, E_ERROR,"Call to undefined function: %s()", fname); } efree(lcase_fname); } |
当执行到php_error_docref()这一行时,内部错误处理器就会明白该错误级别是critical,并相应地调用longjmp()来中断当前程序流程并离开call_function()函数,甚至根本不会执行到efree(lcase_fname)这一行。你可能想把efree()代码行移动到zend_error()代码行的上面;但是,调用这个call_function()例程的代码行会怎么样呢?fname本身很可能就是一个分配的字符串,并且,在它被错误消息处理使用完之前,你根本不能释放它。
注意,这个php_error_docref()函数是trigger_error()函数的一个内部等价实现。它的第一个参数是一个将被添加到docref的可选的文档引用。第三个参数可以是任何我们熟悉的E_*家族常量,用于指示错误的严重程度。第四个参数(最后一个)遵循printf()风格的格式化和变量参数列表式样。
http://www.php-internals.com/book/?p=chapt06/06-00-memory-management
memory management
第六章 内存管理
内存是计算机非常关键的部件之一,是暂时存储程序以及数据的空间,CPU只有有限的寄存器可以用于 存储计算数据,而大部分的数据都是存储在内存中的,程序运行都是在内存中进行的。和CPU计算能力一样, 内存也是决定计算效率的一个关键部分。
计算中的资源中主要包含:CPU计算能力,内存资源以及I/O。现代计算机为了充分利用资源, 而出现了多任务操作系统,通过进程调度来共享CPU计算资源,通过虚拟存储来分享内存存储能力。 本章的内存管理中不会介绍操作系统级别的虚拟存储技术,而是关注在应用层面: 如何高效的利用有限的内存资源。
目前除了使用C/C++等这类的低层编程语言以外,很多编程语言都将内存管理移到了语言之后, 例如Java, 各种脚本语言:PHP/Python/Ruby等等,程序手动维护内存的成本非常大, 而这些脚本语言或新型语言都专注于特定领域,这样能将程序员从内存管理中解放出来专注于业务的实现。 虽然程序员不需要手动维护内存,而在程序运行过程中内存的使用还是要进行管理的, 内存管理的工作也就编程语言实现程序员的工作了。
内存管理的主要工作是尽可能高效的利用内存。
内存的使用操作包括申请内存,销毁内存,修改内存的大小等。 如果申请了内存在使用完后没有及时释放则可能会造成内存泄露,如果这种情况出现在常驻程序中, 久而久之,程序会把机器的内存耗光。所以对于类似于PHP这样没有低层内存管理的语言来说, 内存管理是其至关重要的一个模块,它在很大程度上决定了程序的执行效率。
在PHP层面来看,定义的变量、类、函数等实体在运行过程中都会涉及到内存的申请和释放, 例如变量可能会在超出作用域后会进行销毁,在计算过程中会产生的临时数据等都会有内存操作, 像类对象,函数定义等数据则会在请求结束之后才会被释放。在这过程中何时申请内存及释放内存就比较关键了。 PHP从开始就有一套属于自己的内存管理机制,在5.3之前使用的是经典的引用计数技术, 但引用计数存在一定的技术缺陷,在PHP5.3之后,引入了新的垃圾回收机制,至此,PHP的内存管理机制更加完善。
本章将介绍PHP语言实现中的内存管理技术实现。
第一节 内存管理概述
从某个意义上讲,资源总是有限的,计算机资源也是如此,衡量一个计算机处理能力的指标有很多, 根据不同的应用需要也会有不同的指标,比如3D游戏对显卡的性能有要求,而Web服务器对吞吐量及响应时间有要求, 通常CPU、内存及硬盘的读取和计算速度具有决定性的作用,在同一时刻这些资源是有限的, 正是因为有限我们才需要合理的利用他们。
操作系统的内存管理
当计算机的电源被打开之后,不管你使用的是什么操作系统,这些软件可能已经在使用内存了。 这是由计算机的结构决定的,操作系统也是一种软件,只不过它是比较特殊的软件, 管理计算机的所有资源,普通应用程序和操作系统的关系有点像老师和学生,老师通常管理一切, 而学生的行为会受到老师或学校规定的限制,例如普通应用程序无法直接访问物理内存或者其他硬件资源。
操作系统直接管理着内存,所以操作系统也需要进行内存管理,内存管理是如此之重要, 计算机中通常都有内存管理单元(MMU) 用于处理CPU对内存的访问。
应用层的内存管理
由于计算机的内存由操作系统进行管理,所以普通应用程序是无法直接对内存进行访问的, 应用程序只能向操作系统申请内存,通常的应用也是这么做的,在需要的时候通过类似malloc之类的库函数 向操作系统申请内存,在一些对性能要求较高的应用场景下是需要频繁的使用和释放内存的, 比如Web服务器,编程语言等,由于向操作系统申请内存空间会引发系统调用, 系统调用和普通的应用层函数调用性能差别非常大,因为系统调用会将CPU从用户态切换到内核, 因为涉及到物理内存的操作,只有操作系统才能进行,而这种切换的成本是非常大的, 如果频繁的在内核态和用户态之间切换会产生性能问题。
鉴于系统调用的开销,一些对性能有要求的应用通常会自己在用户态进行内存管理, 例如第一次申请稍大的内存留着备用,而使用完释放的内存并不是马上归还给操作系统, 可以将内存进行复用,这样可以避免多次的内存申请和释放所带来的性能消耗。
PHP不需要显式的对内存进行管理,这些工作都由Zend引擎进行管理了。PHP内部有一个内存管理体系, 它会自动将不再使用的内存垃圾进行释放,这部分的内容后面的小节会介绍到。
<?php echo memory_get_usage()."\r\n"; ini_set("memeory_limit","0.01M"); $w = array(); $wb = array(); for($wi=0;$wi<9999;$wi++){ $w[] = $wi.'abcdefgh'; $wb[] = $w; echo memory_get_usage()."\r\n"; }
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 35 bytes) in D:\php\php.php on line 7 Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 35 bytes) in D:\php\php.php on line 7
<?php echo memory_get_usage()."\r\n"; ini_set("memeory_limit","1M"); $w = array(); $wb = array(); for($wi=0;$wi<9999;$wi++){ $w[] = $wi.'abcdefgh'; $wb[] = $w; echo memory_get_usage()."\r\n"; }
35M怎么计算得到的?
这个错误的信息很明确,PHP已经达到了允许使用的最大内存了,通常上来说这很有可能是我们的程序编写的有些问题。 比如:一次性读取超大的文件到内存中,或者出现超大的数组,或者在大循环中的没有及时释放掉不再使用的变量, 这些都有可能会造成内存占用过大而被终止。
PHP默认的最大内存使用大小是32M, 如果你真的需要使用超过32M的内存可以修改php.ini配置文件的如下配置:
如果你无法修改php配置文件,如果你的PHP环境没有禁用ini_set()函数,也可以动态的修改最大的内存占用大小:
既然我们能动态的调整最大的内存占用,那我们是否有办法获取目前的内存占用情况呢?答案是肯定的。
- memory_get_usage(),这个函数的作用是获取 目前PHP脚本所用的内存大小。
- memory_get_peak_usage(),这个函数的作用返回 当前脚本到目前位置所占用的内存峰值,这样就可能获取到目前的脚本的内存需求情况。
单就PHP用户空间提供的功能来说,我们似乎无法控制内存的使用,只能被动的获取内存的占用情况, 这样的话我们学习内存管理有什么用呢?
前面的章节有介绍到引用计数,函数表,符号表,常量表等。当我们明白这些信息都会占用内存的时候, 我们可以有意的避免不必要的浪费内存,比如我们在项目中通常会使用autoload来避免一次性把不一定会使用的类 包含进来,而这些信息是会占用内存的,如果我们及时把不再使用的变量unset掉之后可能会释放掉它所占用的空间,
“unset只是将引用关系打破”
前面之所以会说把变量unset掉时候可能会把它释放掉的原因是: 在PHP中为了避免不必要的内存复制,采用了引用计数和写时复制的技术, 所以这里unset只是将引用关系打破,如果还有其他变量指向该内存, 它所占用的内存还是不会被释放的。
当然这还有一种情况:出现循环引用,这个就得靠gc来处理了, 内存不会当时就释放,只有在gc环节才会被释放。
https://zh.cppreference.com/w/cpp/string/byte/memset
https://baike.baidu.com/item/memset
memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。