zoukankan      html  css  js  c++  java
  • PHP内存管理和垃圾回收机制

    PHP垃圾回收机制

    GC,垃圾回收器,全称Garbage Collection

    早期版本,准确地说是5.3之前(不包括5.3)的垃圾回收机制,是没有专门的垃圾回收器的。

    只是简单的判断了一下变量的zval的refcount是否为0,是的话就释放否则不释放直至进程结束。

     

    php引用计数基本知识点

    关于php的zval结构体,以及refcount与is_ref的知识点

    不准确但却通俗的说:
    refcount:多少个变量是一样的用了相同的值,这个数值就是多少。
    is_ref:bool类型,当refcount大于2的时候,其中一个变量用了地址&的形式进行赋值,好了,它就变成1了。

     

    首先需要在php上装上xdebug的扩展。

    1.第一步:查看内部结构

    <?php
    $name = "咖啡色的羊驼";
    xdebug_debug_zval('name');
    会得到:

    name:(refcount=1, is_ref=0),string '咖啡色的羊驼' (length=18)
    2.第二步:增加一个计数

    <?php
    $name = "咖啡色的羊驼";
    $temp_name = $name;
    xdebug_debug_zval('name');
    会得到:

    name:(refcount=2, is_ref=0),string '咖啡色的羊驼' (length=18)
    看到了吧,refcount+1了。

    3.第三步:引用赋值

    <?php
    $name = "咖啡色的羊驼";
    $temp_name = &$name;
    xdebug_debug_zval('name');
    会得到:

    name:(refcount=2, is_ref=1),string '咖啡色的羊驼' (length=18)
    是的引用赋值会导致zval通过is_ref来标记是否存在引用的情况。

    4.第四步:数组型的变量

    <?php
    $name = ['a'=>'咖啡色', 'b'=>'的羊驼'];
    xdebug_debug_zval('name');

    会得到:

    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)

    对于数组来看是一个整体,对于内部kv来看又是分别独立的整体,各自都维护着一套zval的refount和is_ref。

    5.第五步:销毁变量

    <?php
    $name = "咖啡色的羊驼";
    $temp_name = $name;
    xdebug_debug_zval('name');
    unset($temp_name);
    xdebug_debug_zval('name');

    会得到:

    name:(refcount=2, is_ref=0),string '咖啡色的羊驼' (length=18)
    name:(refcount=1, is_ref=0),string '咖啡色的羊驼' (length=18)

    refcount计数减1,说明unset并非一定会释放内存,当有两个变量指向的时候,并非会释放变量占用的内存,只是refcount减1.

    php的内存管理机制

    外在的内存变化

    先来一段代码:

    <?php
    //获取内存方法,加上true返回实际内存,不加则返回表现内存
    var_dump(memory_get_usage());
    $name = "咖啡色的羊驼";
    var_dump(memory_get_usage());
    unset($name);
    var_dump(memory_get_usage());

    会得到:

    int 1593248
    int 1593384
    int 1593248

    大致过程:定义变量->内存增加->清除变量->内存恢复

    潜在的内存变化

    当执行:$name = "咖啡色的羊驼";

    时候,内存的分配做了两件事情: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());

    会得到:

    int 1596864
    int 1612080
    int 1597680

    怎么和之前看的不一样?内存没有全部回收回来。

    对于php的核心结构Hashtable来说,由于未知性,定义的时候不可能一次性分配足够多的内存块。所以初始化的时候只会分配一小块,等不够的时候在进行扩容,而Hashtable只扩容不减少,所以就出现了上述的情况:当存入100个变量的时候,符号表不够用了就进行一次扩容,当unset的时候只释放了”为变量值分配内存”,而“为变量名分配内存”是在符号表的,符号表并没有缩小,所以没收回来的内存是被符号表占去了。

    潜在的内存申请与释放设计

    php和c语言一样,也是需要进行申请内存的,只不过这些操作作者都封装到底层了,php使用者无感知而已。

    php的内存申请小设计
    php并非简单的向os申请内存,而是会申请一大块内存,把其中一部分分给申请者,这样当再有逻辑来申请内存的时候,就不需要向os申请了,避免了频繁调用。当内存不够的时候才会再次申请

    php的内存释放小设计
    当释放内存的时候,php并非会把内存还给os,而是把内存轨道自己维护的空闲内存列表,以便重复利用,

    php中垃圾是如何定义的?

    准确地说,判断是否为垃圾,主要看有没有变量名指向变量容器zval,如果没有则认为是垃圾,需要释放。

    打个比方:

    <?php
    $name = "咖啡色的羊驼";
    // todo other things

    当定义name的时候,处理完字符串准备做其他事情的时候,对于我们来说name的时候,处理完字符串准备做其他事情的时候,对于我们来说name就是可以回收的垃圾了,然而对于引擎来说,$name还是实打实存在的refcount也还是1,所以就不是垃圾,不能回收。当调用unset的时候,也并不一定引擎会认为它是一个垃圾而进行回收,主要还是看refcount是不是真的变为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就是一个垃圾

    为了避免每次变量的refcount减少的时候都调用GC的算法进行垃圾判断,此算法会先把所有前面准则3情况下的zval节点放入一个节点(root)缓冲区(root buffer),并且将这些zval节点标记成紫色,同时算法必须确保每一个zval节点在缓冲区中之出现一次。当缓冲区被节点塞满的时候,GC才开始开始对缓冲区中的zval节点进行垃圾判断。

    涉及到垃圾回收的知识点

    1.unset函数
    unset只是断开一个变量到一块内存区域的连接,同时将该内存区域的引用计数-1;内存是否回收主要还是看refount是否到0了,以及gc算法判断。

    2.= null 操作;
    a=null是直接将a=null是直接将a 指向的数据结构置空,同时将其引用计数归0。

    3.脚本执行结束
    脚本执行结束,该脚本中使用的所有内存都会被释放,不论是否有引用环。


  • 相关阅读:
    php数组
    php 函数
    数据库操作
    PHP基础
    mysql常用函数
    10.25 (下午) 开课一个月零二十一天(抽象)
    10.25 (上午) 开课一个月零二十一天 (继承多态)
    10.24 (下午) 开课一个月零二十天 (封装)
    10.24 (上午) 开课一个月零二十天 (面向对象)
    10.23 开课一个月零十九天 (PHP数组)
  • 原文地址:https://www.cnblogs.com/jdbeyond/p/11298064.html
Copyright © 2011-2022 走看看