今天在群里看到一个问题 “PHP执行中内存是什么样子的?”,我还真不知道…找了点资料
内存,是什么
1.来自硬件的内存
2.来自软件的内存
内存映像是按 段 来分配的
1.文本(Text)这个一般就是代码段了 通常用来存储程序执行的代码 比如函数和方法。代码段需要防止在运行时被非法修改,所以只准读取操作,而不允许写入(修改)操作。
2.数据(Data)通常是指用来存放程序中已初始化且不为0的全局变量,如:静态变量和常量。
3.堆(Heap) 用来存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。用于存储数据长度可变或占用内存比较大的数据。例如,字符串、数组和对象就存储在这段内存中。
4.栈(Stack) 他的特点是空间小但被CPU访问的速度快,是用户存放程序中临时创建的变量。由于栈的后进先出特点,所以栈特别方便用来保存和恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个临时数据寄存、交换的内存区。用于存储占用空间长度不变且占用空间小的数据类型的内存段,例如整型1、100、100000等在内存中占用空间是等长的,占用的空间都是32位4个字节。还有double、boolean等都可以存储在栈空间段中。
Zend内存管理
Zend Memory Manager(Zend内存管理) 就是php内部请求绑定堆分配器。
它会一次申请很多的内存放在内存池中,当我们需要内存的时候就会从内存池当取出一块,然后判断有没有时候的内存给调用,如果够的话直接给一块内存 如果不够的话ZendMM就会再次向系统申请内存,另外他里面还有个CopyOnWrite(复制代替写入?)的机制也可以提高内存管理的效率,对了还有垃圾回收机制!
可以通过 phpinfo()
来查看 如果发现 有 Zend Memory Manager = enabled
就表示开启了Zend内存管理
允许通过基本计算 malloc() / free() 调用来监视请求限制的堆使用
允许PHP用户限制堆内存使用量
允许缓存已分配的块以防止内存碎片和系统调用
允许预先分配PHP内部结构的已知大小的块以对齐的方式适应
简化核心和扩展中的内存泄露调试
看一个Zend内存管理的例子
|
|
PHP内存函数
在PHP中有些函数可以设置与获取内存
ini_set(‘memory_limit’, xxx); // 这个可以设置内存
memory_get_usage(); // 返回分配给PHP的内存量
memory_get_usage(true); // 返回所有已分配段的大小
memory_get_peak_usage(); // 返回已分配给PHP脚本的内存峰值(以字节为单位)
说下 memory_get_usage()
函数 它只显示请求绑定的分配,而不是持久分配(通过请求驻留)。PHP扩展可以分配持久内存,如果你不会用到他们的话不要启用。被PHP用到的库也会分配持久内存,使用你的系统来准确的监控你的进程内存消耗
管理PHP内存
在PHP中 所有的变量类型都会消耗内存,每个要被编译的脚本都会吃掉内存, 这部分内存可以使用zned内存管理来分配。请求结束时释放已解析脚本的内存。
当用户变量不在使用的时候 这部分用户变量将会被释放,虽然是这么讲 这里会有个问题 什么时候这些变量才是不被需要 不会在使用的呢?(垃圾回收机制)
编译脚本的时候会占用请求限制的内存。如果你编译了一个类,那将会吃掉更多的内存,所以最好是在运行时使用它(那个类) 并确保使用了 autoloader
自动加载
在PHP中所有的变量在底层都是一个 zval 结构
|
|
吃掉内存的时候 zval 中的东西 而不是 zval 结构本身,比如一个很长的字符串 一个很复杂的数组或者对象,资源类型不会真正的消耗zval中的内存。
尽量避免让PHP复制zval,另外PHP只计算有多少符号指向zval 这是他的引用计数
PHP对zval操作是使用复制代替写入的系统,只有当发生改变的时候内存才会得到分配
如上图(注意引用计数的变化) 假设我们执行了4行代码
当执行 $a = 'foo';
的时候首先会在内存申请一块空间 其中存有他的值 和 他的引用计数
当执行 $b = $a;
的时候 $a 和 $b 都是指向了同一块内存地址
当执行 $a = 17;
的时候,$a 发生了类型改变 这个时候就会重新分配内存 所以发生了上面的操作 $b 还是指向一开始申请的内存地址 也就是字符串 foo 的内存,而$a 已经指向了新申请的内存地址 int 17上
当执行 $b = null;
的时候会回收内存 $b 就不存在了 就只剩下 $a了
看下内存消耗情况 代码太渣 直接看结果吧 因为在浏览器输出的 加了 <br/>
为了明显看出内存的变化 我把 $a = 17
换了重新赋值字符串 这样内存变化比较明显
|
|
|
|
可以看出声明了变量 $a 后内存变成了 1376.51 可以计算得出$a占用内存 1028 Kb
当执行$b = $a; 的时候只是把$b指向了同一块内存地址 堆内存中并没有发生改变 内存大小没变
当执行$a = str_repeat(‘b’, 1024); 的时候$a发生了改变需要重新分配内存 所以$a重新开辟了内存出来, 而$b还是指向原来的内存地址(那个地址还是占用1028kb) 这时候的内存因为$a新分配的内存而增加了 变成 1377.76kb 可以计算出新的$a的 内存是 1.25kb
当执行 $b = null; 的时候相当于释放了$b 也就是他指向的那块内存(大小1028kb)被释放,所以后面获取的内存是 1377.76-1028 = 349.76kb
PHP用户空间内存泄露
|
|
如上代码 最后你会发现即使释放了$a 和 $b 内存也没有减少,因为这种情况下,对象仍然存在内存当中即使没有别的东西引用他但他的引用计数是1 所以还会占用内存 这种就是PHP的用户空间内存泄露
监控内存
Linux下可以使用 top
命令等查看
|
|
找到上面的 pid
比如上面 910 的 docker 然后执行 cat /proc/910/status
|
|
还可以通过 cat /proc/910/smaps
来查看更具体的内存
|
|
PHP他本身也只是像其他进程一样 只是个进程而已,也可以使用上面的系统命令来监控他的内存
|
|
参考
http://www.laruence.com/2008/08/22/412.html;
http://mars.run/2016/01/Understanding-PHP-memory-management/
https://gywbd.github.io/posts/2015/4/php-variable-in-memory.html
https://www.slideshare.net/jpauli/understanding-php-memory