PHP的垃圾回收机制以及大概实现
垃圾回收,简称gc。顾名思义,就是废物重利用的意思。再说这个之前先接触一下内存泄露,大概意思就是申请了一块地儿拉了会儿屎,拉完之后不收拾,那么这块地就算糟蹋了,地越用越少,最后一地全是屎。说到底一句,用了记得还。一定程度上说,垃圾回收机制就是来擦屁股的。
如果用过C语言,那么申请内存的方式是malloc或者calloc,然后你用完这个内存之后,一定不要忘记用free函数去释放掉,这就是传说中的手动垃圾回收,一般都是要扫地僧用这种方式。
很多高层次语言中,你这辈子都是接触不到内存管理的,比如世界上最好的语言PHP,这种语言替你管理了内存,你就安安心心写烂代码即可。写PHP的,你说你关心内存,我是不怎么相信的,一定是你在装逼。当然了,如果你用的swoole或者wm或者自己发明的常驻内存级PHP应用,那你将不得不关注内存泄露的问题,也就说一定要记得释放无用变量。
那么,在用的最普遍地最传统的web开发中,PHP的自动垃圾回收机制是怎样的呢?
这个问题我们先这么想,就是都知道PHP是C语言实现的,现在把C语言给你放在这里了,然后你想如果用C语言实现对一个变量的统计以及释放。你不要想如何实现PHP,你就想C语言如何实现一个变量,从声明开始到最后没人用了,就把这个变量所占的内存释放掉。你从这个角度出发,就会舒服一些,这不再是一个技术难题,而是一个傻逼产品经理提的一个傻逼需求。
好了,步入正题,PHP进行内存管理的核心算法一共两项:一是引用计数,二是写时拷贝,请理(bei)解(song)。当你声明一个PHP变量的时候,C语言就在底层给你搞一个zval的struct(结构体);如果你还给这个变量赋值了,比如“hello world”,那么C语言就在底层再给你搞一个zend_vaue的union(联合体),总体看来是这样的:
1、我用的PHP版本是7.0.12(记住!这个很重要!不同版本的PHP有极大可能会出现不同的结果,我试过6个版本的PHP,三个PHP5版本,三个PHP7版本,其中PHP版本变化尤其多,但不影响业务代码不会出BUG,放心),运行环境是cli。
2、下面的原因只针对PHP7,不再说5了。你面试的时候,只需要说5的我不太了解,7的我深入看过一些即可,面试官不会难为你的。
$a = 'hello'.mt_rand(1,1000);
echo xdebug_debug_zval('a'); $b = $a;
echo xdebug_debug_zval('a'); $c = $a;
echo xdebug_debug_zval('a'); unset($c);
echo xdebug_debug_zval('a');
输出的结果是:
a:
(refcount=1, is_ref=0)string 'hello204' (length=8)
a:
(refcount=2, is_ref=0)string 'hello204' (length=8)
a:
(refcount=3, is_ref=0)string 'hello204' (length=8)
a:
(refcount=2, is_ref=0)string 'hello204' (length=8)
其中,zval struct结构体用于保存$a,zend_value union联合体用于保存数据内容‘hello204’。由于后面又声明了b和c,所以C不得不又在底层给你搞出两个zval struct结构体来。
其中,zval和zend_value的结构大概如下:(注意!!!这并不是完全正确的PHP zval和zend_value在C语言中的struct和union实现,仅仅是挑出最重点的部分写出来,强调一下:你没必要一字不差背诵zval和zend_value,你只需要知道原理)。
zval {
string "a" //变量的名字是a
value zend_value //变量的值
type string //变量是字符串类型
}
zend_value {
string "hello204" //值的内容
refcount 1 //引用计数
}
看到上面两个,如果面试官问你PHP变量为什么能够保存字符串‘123’,也能保存数字123,你知道该怎么回答吧?就答出重点zval中有该变量的类型,当是字符串123的时候,type就是string,此时value指向“123”;当是整数123的时候,zval的type是int,value是123.这就是答题的思想,这很重要!而且,通过C语言都是可以实现的!具体真正的val和zend_value的模样,有兴趣的同学可以去网上搜,如果你没有C语言的底子,可能比较吃力!前者是一个struct结构体,后者是一个union联合体。
这个refcount就是传说中的引用计数,初始化的时候a后面的引用次数为1(注意,正确说法应该是a后面的赋值的数组的zend_value引用计数为1,而不是a这个变量zval本身)。然后我们将$b = $a,其实相当于又将一个变量指向了这个zend_value,所以refcount变为2,左后将$c = $a,同理,zend_value的refcount再次加1变成3.然后我们用unset($c),这会儿,C语言要做的就是把$c的zval给KO free掉,但是并不是free zend_value,这会儿zend_value的refcount就自然而然减1变成2了。
那么写时拷贝是什么意思呢?看下面的代码:
$a = 'hello'.mt_rand(1,1000);
$b = $a;
$a = 123;
echo $b.PHP_EOL;
运行结果是:
hello607
其实,当你把$a赋值给$b的时候,$a的值并没有真的复制一份,这样是对内存的极度不尊重,也是对时间复杂度的极度不尊重,计算机仅仅是将$b指向了$a的值而已,这就叫多快好省。那么,什么时候真正的发生复制呢?就是当我们修改$a的值为123的时候,这个时候就不得已进行复制,避免$b的值和$a的一样。
$a = 'hello'.mt_rand(1,1000);
$b = $a;
echo xdebug_debug_zval('a');
$a = 'world'.mt_rand(2,2000);
echo xdebug_debug_zval('a');
echo xdebug_debug_zval('b');
运行结果是:
a:
(refcount=2, is_ref=0)string 'hello642' (length=8)
a:
(refcount=1, is_ref=0)string 'world1639' (length=9)
b:
(refcount=1, is_ref=0)string 'hello642' (length=8)
叨逼叨了这么长时间,通过简单的案例解释清楚了两个要点:引用计数和写时拷贝,那么垃圾回收也该来了。当一个zval在被unset的时候或者从一个函数中运行完毕(就是局部变量)的时候等等很多地方,都会产生zval与zend_value发生断开的行为,这个时候zend引擎需要检测的就是zend_value的refcount是否为0,如果为0,则直接KO free空出来被释放。如果zend_value的refcount不为0(废话一定是大于0),这个value不能被释放,但是也不代表这个zend_value是清白的,因为此zend_value依然可能是个垃圾。
什么样的情况会导致zend_value的refcount不为0,但是这个zend_value却是个垃圾呢?PHP7中两种情况:
1、数组:a数组的某个成员使用&引用a自己
2、对象:对象的某个成员引用对象自己
$arr = [1];
$arr[] = &$arr;
unset($arr);
echo xdebug_debug_zval('arr');
这种情况下,zend_value不会被释放,但也不能放过它,不然一定会产生内存泄露,所以这会儿zend_value会被扔到一个叫做垃圾回收堆中,然后zend引擎会以此对垃圾回收堆中的这些zend_value进行二次检测,检测是不是由于上述两种情况造成的refcount为1,但是自身却确实没有人再用了,如果一旦确定是上述两种情况造成的,那么就会将zend_value彻底抹掉释放内存。
那么垃圾回收发生在什么时候?有些同学可能有疑问,就是PHP不是运行一次就销毁了吗,我要gc有何用?并不是啦,首先当一次fpm运行完毕后,最后一定还有gc的,这个销毁就是gc;其次是,内存都是即用即释放的,而不是攒着非得到最后,你想想一个典型的场景,你的控制器里的某个方法里用了一个函数,函数需要一个巨大的数组参数,函数还需要修改这个巨大的数组参数,你们应该是在函数的运行范围里面修改这个数组,所以此时会发生写时拷贝,当函数运行完毕后,就得赶紧释放掉这块内存以供其他进程使用,而不是非得等到本地fpm request彻底完成后才销毁。
说到最后,说些自己的话:大多数情况下,面试官问你的问题主要是想,一是要你个思维思路,二是看你学习程度。就像gc这个问题,其实很多脚本语言的垃圾回收机制基本上都是靠引用计数和写时拷贝这两种算法结合完成的,所以如果你设计一门脚本语言,gc机制就按照这两种算法进行设计即可。其次是大多数phper不会看这些东西,面试官问你这个问题不是要你死记硬背那么多细节,你背不过的,他还是想探测你平时有没有更积极地往深层发展的心态。
PHP的垃圾回收机制以及大概实现的更多相关文章
- 【转】深入理解 Java 垃圾回收机制
深入理解 Java 垃圾回收机制 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...
- 闭包内的微观世界和js垃圾回收机制
一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话 ...
- 深入理解java垃圾回收机制
深入理解java垃圾回收机制---- 一.垃圾回收机制的意义 Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再 ...
- 初识JAVA(【面向对象】:pub/fri/pro/pri、封装/继承/多态、接口/抽象类、静态方法和抽象方法;泛型、垃圾回收机制、反射和RTTI)
JAVA特点: 语法简单,学习容易 功能强大,适合各种应用开发:J2SE/J2ME/J2EE 面向对象,易扩展,易维护 容错机制好,在内存不够时仍能不崩溃.不死机 强大的网络应用功能 跨平台:JVM, ...
- 理解Android Java垃圾回收机制
Jvm(Java虚拟机)内存模型 从Jvm内存模型中入手对于理解GC会有很大的帮助,不过这里只需要了解一个大概,说多了反而混淆视线. Jvm(Java虚拟机)主要管理两种类型内存:堆和非堆.堆是运行时 ...
- 成为Java GC专家(3)—如何优化Java垃圾回收机制
为什么需要优化GC 或者说的更确切一些,对于基于Java的服务,是否有必要优化GC?应该说,对于所有的基于Java的服务,并不总是需要进行GC优化,但前提是所运行的基于Java的系统,包含了如下参数或 ...
- PHP服务器脚本 PHP内核探索:新垃圾回收机制说明
在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果refco ...
- java基础(一):谈谈java内存管理与垃圾回收机制
看了很多java内存管理的文章或者博客,写的要么笼统,要么划分的不正确,且很多文章都千篇一律.例如部分地方将jvm笼统的分为堆.栈.程序计数器,这么分太过于笼统,无法清晰的阐述java的内存管理模型: ...
- JVM基础系列第8讲:JVM 垃圾回收机制
在第 6 讲中我们说到 Java 虚拟机的内存结构,提到了这部分的规范其实是由<Java 虚拟机规范>指定的,每个 Java 虚拟机可能都有不同的实现.其实涉及到 Java 虚拟机的内存, ...
随机推荐
- 第一百九十三节,jQuery EasyUI,Draggable(拖动)组件
Draggable(拖动)组件 学习要点: 1.加载方式 2.属性列表 3.事件列表 4.方法列表 本节课重点了解 EasyUI 中 Draggable(拖动)组件的使用方法,这个组件不依赖于其 他组 ...
- c语言之linux下gettimeofday函数windows替换方案
* Copyright (C) 2008 mymtom (mymtom@hotmail.com) * All rights reserved. * * Redistribution and use i ...
- makefile编写---单个子目录编译模板
经过这次地库项目之后,虽然时间不久,跟团队在一起,虽然队员不一定在技术上有过人之处,但是来自大公司的员工,在工具使用和代码规范方面还是有点可鉴之处,在搭建主控模块是,就得面临makefile编写,因为 ...
- C++11写算法之插入排序
插入排序,是指将从1 –> size-1的数一个个插入到前面已经排序好的数组中. 时间复杂度:O(n^2) , O(nlgn) (lgn指使用二分查找插入点位置) 空间复杂度:O(1) // ...
- C++11写算法之选择排序
选择排序,顾名思义,指从数组后面将最小的值找出来,然后与最前面(指当前位置)值进行交换. 时间复杂度:O(n^2) 空间复杂度:O(1) 此处应用了C++11的auto , lambda , stat ...
- js原型对象中属性被覆盖(1)
/** *@author 程无衣 *@description 关于在原型对象中属性被覆盖 */ function Person(){} Person.prototy ...
- Springboot中读取自定义名称properties的
Springboot读取自定义的配置文件时候,使用@value,一定要指定配置文件的位置! 否则报错参数化异常!
- 搭建Spring所需的各类jar包汇总详解
Spring jar包官网下载地址:http://repo.spring.io/release/org/springframework/spring/ Spring jar包的描述:针对3.2.2以上 ...
- 使用QFileInfo类获取文件信息(在NTFS文件系统上,出于性能考虑,文件的所有权和权限检查在默认情况下是被禁用的,通过qt_ntfs_permission_lookup开启和操作。absolutePath()必须查询文件系统。而path()函数,可以直接作用于文件名本身,所以,path() 函数的运行会更快)
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/Amnes1a/article/details/65444966QFileInfo类为我们提供了系统无 ...
- Python3.6全栈开发实例[002]
2.判断用户传入的对象(字符串.列表.元组)长度是否大于5. li = [11,22,33,44,55,66,77,88,99,000,111,222] def func2(lst): if len( ...