php的垃圾回收机制
转载请附上本文地址:http://blog.csdn.net/u011957758/article/details/76864400
前言
是的,平时经常听到大牛说到的gc,就是垃圾回收器,全称Garbage Collection。
早期版本,准确地说是5.3之前(不包括5.3)的垃圾回收机制,是没有专门的垃圾回收器的。只是简单的判断了一下变量的zval的refcount是否为0,是的话就释放否则不释放直至进程结束。
乍一看确实没毛病啊,然而其中隐藏着变量内存溢出的风险:http://bugs.php.net/bug.php?id=33595 ,无法回收的内存造成了内存泄漏,所以PHP5.3出现了专门负责清理垃圾数据、防止内存泄漏的GC。
下文将由浅入深(凭感觉)来记录下php的垃圾回收机制是怎么一回事?
1.php引用计数基本知识点
2.php的内存管理机制
3.php中垃圾是如何定义的?
4.老版本php中如何产生内存泄漏?
5.5.3版本以后php是如何处理垃圾内存的?
6.涉及到垃圾回收的知识点
php引用计数基本知识点
首先必须要先讲讲这个会引起垃圾回收的关键基数是怎么回事?
关于php的zval结构体,以及refcount与is_ref的知识点,在菜鸟学php扩展 之 详解php扩展的变量(四)已描述非常清楚。
不准确但却通俗的说:
refcount:多少个变量是一样的用了相同的值,这个数值就是多少。
is_ref:bool类型,当refcount大于2的时候,其中一个变量用了地址&的形式进行赋值,好了,它就变成1了。
主要讲讲如何用php来直观的看到这些计数的变化,走一波。
首先需要在php上装上xdebug的扩展。
1.第一步:查看内部结构
<?php
$name = "咖啡色的羊驼";
xdebug_debug_zval('name');
- 1
- 2
- 3
会得到:
name:(refcount=1, is_ref=0),string '咖啡色的羊驼' (length=18)
- 1
2.第二步:增加一个计数
<?php
$name = "咖啡色的羊驼";
$temp_name = $name;
xdebug_debug_zval('name');
- 1
- 2
- 3
- 4
会得到:
name:(refcount=2, is_ref=0),string '咖啡色的羊驼' (length=18)
- 1
看到了吧,refcount+1了。
3.第三步:引用赋值
<?php
$name = "咖啡色的羊驼";
$temp_name = &$name;
xdebug_debug_zval('name');
- 1
- 2
- 3
- 4
会得到:
name:(refcount=2, is_ref=1),string '咖啡色的羊驼' (length=18)
- 1
是的引用赋值会导致zval通过is_ref来标记是否存在引用的情况。
4.第四步:数组型的变量
<?php
$name = ['a'=>'咖啡色', 'b'=>'的羊驼'];
xdebug_debug_zval('name');
- 1
- 2
- 3
会得到:
name:
(refcount=1, is_ref=0),
array (size=2)
'a' => (refcount=1, is_ref=0),string '咖啡色' (length=9)
'b' => (refcount=1, is_ref=0),string '的羊驼' (length=9)
- 1
- 2
- 3
- 4
- 5
- 6
还挺好理解的,对于数组来看是一个整体,对于内部kv来看又是分别独立的整体,各自都维护着一套zval的refount和is_ref。
5.第五步:销毁变量
<?php
$name = "咖啡色的羊驼";
$temp_name = $name;
xdebug_debug_zval('name');
unset($temp_name);
xdebug_debug_zval('name');
- 1
- 2
- 3
- 4
- 5
- 6
会得到:
name:(refcount=2, is_ref=0),string '咖啡色的羊驼' (length=18)
name:(refcount=1, is_ref=0),string '咖啡色的羊驼' (length=18)
- 1
- 2
refcount计数减1,说明unset并非一定会释放内存,当有两个变量指向的时候,并非会释放变量占用的内存,只是refcount减1.
php的内存管理机制
知道了zval是怎么一回事,接下来看看如何通过php直观看到内存管理的机制是怎么样的。
外在的内存变化
先来一段代码:
<?php
//获取内存方法,加上true返回实际内存,不加则返回表现内存
var_dump(memory_get_usage());
$name = "咖啡色的羊驼";
var_dump(memory_get_usage());
unset($name);
var_dump(memory_get_usage());
- 1
- 2
- 3
- 4
- 5
- 6
- 7
会得到:
int 1593248
int 1593384
int 1593248
- 1
- 2
- 3
大致过程:定义变量->内存增加->清除变量->内存恢复
潜在的内存变化
当执行:
$name = "咖啡色的羊驼";
- 1
时候,内存的分配做了两件事情:1.为变量名分配内存,存入符号表 2.为变量值分配内存
再来看代码:
<?php
var_dump(memory_get_usage());
for($i=0;$i<100;$i++)
{
$a = "test".$i;
$$a = "hello";
}
var_dump(memory_get_usage());
for($i=0;$i<100;$i++)
{
$a = "test".$i;
unset($$a);
}
var_dump(memory_get_usage());
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
会得到:
int 1596864
int 1612080
int 1597680
- 1
- 2
- 3
简直爆炸,怎么和之前看的不一样?内存没有全部回收回来。
对于php的核心结构Hashtable来说,由于未知性,定义的时候不可能一次性分配足够多的内存块。所以初始化的时候只会分配一小块,等不够的时候在进行扩容,而Hashtable只扩容不减少,所以就出现了上述的情况:当存入100个变量的时候,符号表不够用了就进行一次扩容,当unset的时候只释放了”为变量值分配内存”,而“为变量名分配内存”是在符号表的,符号表并没有缩小,所以没收回来的内存是被符号表占去了。
潜在的内存申请与释放设计
php和c语言一样,也是需要进行申请内存的,只不过这些操作作者都封装到底层了,php使用者无感知而已。
php的内存申请小设计
php并非简单的向os申请内存,而是会申请一大块内存,把其中一部分分给申请者,这样当再有逻辑来申请内存的时候,就不需要向os申请了,避免了频繁调用。当内存不够的时候才会再次申请
php的内存释放小设计
当释放内存的时候,php并非会把内存还给os,而是把内存轨道自己维护的空闲内存列表,以便重复利用,
php中垃圾是如何定义的?
准确地说,判断是否为垃圾,主要看有没有变量名指向变量容器zval,如果没有则认为是垃圾,需要释放。
打个比方:
<?php
$name = "咖啡色的羊驼";
// todo other things
- 1
- 2
- 3
当定义name的时候,处理完字符串准备做其他事情的时候,对于我们来说name就是可以回收的垃圾了,然而对于引擎来说,$name还是实打实存在的refcount也还是1,所以就不是垃圾,不能回收。当调用unset的时候,也并不一定引擎会认为它是一个垃圾而进行回收,主要还是看refcount是不是真的变为0了。
老版本php中如何产生内存泄漏垃圾?
产生内存泄漏主要真凶:环形引用。
现在来造一个环形引用的场景:
<?php
$a = ['one'];
$a[] = &$a;
xdebug_debug_zval('a');
- 1
- 2
- 3
- 4
得到:
a:
(refcount=2, is_ref=1),
array (size=2)
0 => (refcount=1, is_ref=0),string 'one' (length=3)
1 => (refcount=2, is_ref=1),
&array<
- 1
- 2
- 3
- 4
- 5
- 6
这样 $a数组就有了两个元素,一个索引为0,值为one字符串,另一个索引为1,为$a自身的引用。
此时删掉$a:
<?php
$a = ['one'];
$a[] = &$a;
unset($a);
- 1
- 2
- 3
- 4
如果在小于php5.3的版本就会出现一个问题:$a已经不在符号表了,没有变量再指向此zval容器,用户已无法访问,但是由于数组的refcount变为1而不是0,导致此部分内存不能被回收从而产生了内存泄漏。
5.3版本以后php是如何处理垃圾内存的?
判断处理过程
为解决环形引用导致的垃圾,产生了新的GC算法,遵守以下几个基本准则:
1.如果一个zval的refcount增加,那么此zval还在使用,不属于垃圾
2.如果一个zval的refcount减少到0, 那么zval可以被释放掉,不属于垃圾
3.如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾
are you ok?
来个白话文版:就是对此zval中的每个元素进行一次refcount减1操作,操作完成之后,如果zval的refcount=0,那么这个zval就是一个垃圾
引用php官方手册的配图:
A:为了避免每次变量的refcount减少的时候都调用GC的算法进行垃圾判断,此算法会先把所有前面准则3情况下的zval节点放入一个节点(root)缓冲区(root buffer),并且将这些zval节点标记成紫色,同时算法必须确保每一个zval节点在缓冲区中之出现一次。当缓冲区被节点塞满的时候,GC才开始开始对缓冲区中的zval节点进行垃圾判断。
B:当缓冲区满了之后,算法以深度优先对每一个节点所包含的zval进行减1操作,为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。需要强调的是,这个步骤中,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。
C:算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成白色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色(zval的默认颜色属性)
D:遍历zval节点,将C中标记成白色的节点zval释放掉。
are you ok?
来个白话文版的:
例如:
<?php
$a = ['one']; --- zval_a(将$a对应的zval,命名为zval_a)
$a[] = &$a; --- step1
unset($a); --- step2
- 1
- 2
- 3
- 4
为进行unset之前(step1),进行算法计算,对这个数组中的所有元素(索引0和索引1)的zval的refcount进行减1操作,由于索引1对应的就是zval_a,所以这个时候zval_a的refcount应该变成了1,这样说明zval_a不是一个垃圾不进行回收。
当执行unset的时候(step2),进行算法计算,由于环形引用,上文得出会有垃圾的结构体,zval_a的refcount是1(zval_a中的索引1指向zval_a),用算法对数组中的所有元素(索引0和索引1)的zval的refcount进行减1操作,这样zval_a的refcount就会变成0,于是就认为zval_a是一个需要回收的垃圾。
算法总的套路:对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。
算法优化配置
可能会发现,每次都进行这样的操作好像会影响性能,是的,php做事情套路都是走批量的原则。
申请内存也是申请一大块,仅使用当前的一小部分剩下的等下回再用,避免多次申请。
这个gc算法也是这样,会有一个缓冲区的概念,等缓冲区满了才会一次性去给清掉。
开关配置
php.ini中设置 zend.enable_gc 项来开启或则关闭GC。
缓冲区配置
缓冲区默认可以放10,000个节点,当缓冲区满了才会清理。可以通过修改Zend/zend_gc.c中的GC_ROOT_BUFFER_MAX_ENTRIES 来改变这个数值,需要重新编译链接PHP
关键函数
gc_enable() : 开启GC
gc_disable() : 关闭GC
gc_collect_cycles() : 在节点缓冲区未满的情况下强制执行垃圾分析算法
涉及到垃圾回收的知识点
1.unset函数
unset只是断开一个变量到一块内存区域的连接,同时将该内存区域的引用计数-1;内存是否回收主要还是看refount是否到0了,以及gc算法判断。
2.= null 操作;
a=null是直接将a 指向的数据结构置空,同时将其引用计数归0。
3.脚本执行结束
脚本执行结束,该脚本中使用的所有内存都会被释放,不论是否有引用环。
php的垃圾回收机制的更多相关文章
- .net垃圾回收机制编程调试试验
1. 什么是CLR GC? 它是一个基于引用跟踪和代的垃圾回收器. 从本质上,它为系统中所有活跃对象都实现了一种引用跟踪模式,如果一个对象没有任何引用指向它,那么这个对象就被认为是垃圾对象,并且可以被 ...
- JavaScript具有自动垃圾回收机制
JavaScript具有自动垃圾回收机制 原理: 找出那些不再继续使用的变量,然后释放其占用的内存. 正常的生命周期: 局部变量指在函数执行的过程中存在.而在这个过程中,会为局部变量在栈或 ...
- java垃圾回收机制
1 .垃圾回收机制(GC)垃圾回收就是回收内存中不再使用对象:(1)垃圾回收的步骤:1)查找内存中不再使用的对象:2)释放这些对象所占用的内存:(2)查找内存中不再使用的对象方法:1)引用计数法如果一 ...
- 垃圾回收机制GC知识再总结兼谈如何用好GC
一.为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1.为对应的资源分配内存 2.初始化内存 3.使用资源 4.清理资源 5.释放内存 应用程序对资源(内存使用)管理的方式,常见的一般 ...
- 【转载】Java垃圾回收机制
原文地址:http://www.importnew.com/19085.html Java垃圾回收机制 说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联 ...
- 【转】深入理解 Java 垃圾回收机制
深入理解 Java 垃圾回收机制 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...
- JAVA的垃圾回收机制
1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的 ...
- Python的垃圾回收机制
Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾.在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的 ...
- python垃圾回收机制的一些理解
概览: 主要通过 引用计数来进行垃圾收集, 就是说,当一个对象没有被其他对象引用的时候,会释放掉内存. 但是会有一些循环引用的对象,通过上面的方法,是没有办法清除掉的.所以,pyt ...
- 闭包内的微观世界和js垃圾回收机制
一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话 ...
随机推荐
- rows的参数
ds.Tables[0].Rows[0][0].ToString()中的rows后边的那俩参数分别代表什么 第一个0表示行的索引(如果是0就表示第一行,1表示第二行……)第二个0表示列的索引(如果是0 ...
- Xcode调试LLDB
一.简介 关于Xcode调试,相信大家很多会用断点调试,今天无意间在苹果开发的群里看到了po,瞬间心中有个疑问:po是什么?下面我就百度搜索了一下,介绍一点皮毛. 首先是LLDB,它的全名是lower ...
- CSS命名方式=》BEM
时间:2016-11-04 20:04:53 原文地址:https://github.com/zhongxia245/blog/issues/48 一.背景 挺早就听说过BEM了,也大概的知道怎么用, ...
- golang []byte和string相互转换
测试例子 package main import ( "fmt" ) func main() { str2 := "hello" ...
- [NOIP2018]赛道修建
嘟嘟嘟 因为一些知道的人所知道的,不知道的人所不知道的原因,我来改写今年的NOIP了. 现在看这题,心中满是疑问:我当时是多么的zz,这种水题为啥没做出来-- 不管了,说正事. 先考虑部分分. 1.\ ...
- 如何在自己设计的页面中调用metamask-2
参考: 1)https://www.colabug.com/3204345.html 2)https://www.toptal.com/ethereum/one-click-login-flows-a ...
- rac添加新节点的步骤与方法(官方步骤与自我测试)
Extending the Oracle Grid Infrastructure Home to the New NodeNow that the new node has been configur ...
- PAT A1109 Group Photo (25 分)——排序
Formation is very important when taking a group photo. Given the rules of forming K rows with N peop ...
- PAT A1019 General Palindromic Number (20 分)——回文,进制转换
A number that will be the same when it is written forwards or backwards is known as a Palindromic Nu ...
- ASP.NET Core读取AppSettings (转载)
今天在把之前一个ASP.NET MVC5的Demo项目重写成ASP.NET Core,发现原先我们一直用的ConfigurationManager.AppSettings[]读取Web.config中 ...