<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 引用計數和寫時拷貝 ## 參考 [引用計數基本知識](https://www.php.net/manual/zh/features.gc.refcounting-basics.php) [PHP的垃圾回收機制](https://www.cnblogs.com/xuxubaobao/p/10840176.html) [# PHP內核探索之變量---變量的容器-Zval](https://blog.csdn.net/ohmygirl/article/details/41542445) ## 1.引用計數(標量類型) 在上一章我們已經講解了PHP變量的結構,其中了解到ref\_count\_\_gc和is\_ref\_\_gc是PHP的GC機制所需的很重要的兩個字段,我們就寫幾個例子來看一下這兩個變量是如何變化的。 **1.1創建變量時,會創建一個zval** ~~~ $str = "test zval"; xdebug_debug_zval('str'); 結果:str: (refcount=1, is_ref=0)='test zval' ~~~ 當使用$str="test zval";來創建變量時,會在當前作用域的符號表中插入新的符號(str),由于該變量是一個普通的變量,因此會生成一個refcount=1且is\_ref=0的zval容器。也就是說,實際上是這樣的: ![](https://img.kancloud.cn/45/44/45443f0f62df0451c6d3a09e2cb182f1_363x144.png) **1.2變量賦值給另外一個變量時,會增加zval的refcount值.** ~~~ $str = "test zval"; $str2 = $str; xdebug\_debug\_zval('str'); xdebug\_debug\_zval('str2'); 結果: str: (refcount=2, is_ref=0)='test zval' str2: (refcount=2, is_ref=0)='test zval' ~~~ 同時我們看到,str和是str2這兩個symbol的zval結構是一樣的。這里其實是PHP所做的一個優化,由于str和str2都是普通變量,因而它們指向了同一個zval,而沒有為str2開辟單獨的zval。這么做,可以在一定程度上節省內存。這時的str,str2與zval的對應關系是這樣的: ![](https://img.kancloud.cn/52/22/5222f9bcc493c6c1335d5cee90b44db0_401x174.png) **1.3使用unset時,對減少相應zval的refcount值** ~~~ $str = "test zval"; $str3 = $str2 = $str; xdebug\_debug\_zval('str'); unset($str2,$str3) xdebug\_debug\_zval('str'); 結果: str: (refcount=3, is_ref=0)='test zval' str: (refcount=1, is_ref=0)='test zval' ~~~ 由于unset($str2,$str3)會將str2和str3從符號表中刪除,因此,在unset之后,只有str指向該zval,如下圖所示: ![](https://img.kancloud.cn/bc/0d/bc0d01b1db011b4d85827a074ae0c770_975x278.png) 現在如果執行unset($str),則由于zval的refcount會減少到0,該zval會從內存中清理。這當然是最理想的情況。 但是事情并不總是那么樂觀。 ## 2.引用計數(復合類型) 當考慮像array和object這樣的復合類型時,事情就稍微有點復雜. 與標量(scalar)類型的值不同,array和object類型的變量把它們的成員或屬性存在自己的符號表中。這意味著下面的例子將生成三個zval變量容器。 **2.1創建數組類型** 與標量這些普通變量不同,數組和對象這類復合型的變量在生成zval時,會為每個item項生成一個zval容器。例如: ~~~ $ar=array( 'id'=> 38, 'name'=>'shine' ); xdebug\_debug\_zval('str'); 結果: ar: (refcount=1, is_ref=0)=array ( 'id' => (refcount=1, is_ref=0)=38, 'name' => (refcount=1, is_ref=0)='shine' ) ~~~ ![](https://img.kancloud.cn/f0/b0/f0b042093232a9e19116f337eac4d7ca_752x405.png) **2.2添加一個已經存在的元素到數組中** 可以看出,變量$arr生成的過程中,共生成了3個zval容器(紅色部分標注)。對于每個zval而言,refcount的增減規則與普通變量的相同。例如,我們在數組中添加另外一個元素,并把$arr\['name'\]的值賦給它: ~~~ $ar=array( 'id'=> 38, 'name'=>'shine', ); $ar['test']=$ar[’name‘]; xdebug_debug_zval('str'); 結果: arr: (refcount=1, is_ref=0)=array ( 'id' => (refcount=1, is_ref=0)=38, 'name' => (refcount=1, is_ref=0)='shine', 'test' => (refcount=1, is_ref=0)='shine', ) ~~~ 如同普通變量一樣,這時候,name和test這兩個symbol指向同一個zval: ![](https://img.kancloud.cn/7b/a2/7ba2e0f256c67b572fabde9bfd4a5a40_736x316.png) **2.2unset刪除一個已經存在的元素到數組中** 同樣的,從數組中移除元素時,會從符號表中刪除相應的符號,同時減少對應zval的refcount值。同樣,如果zval的refcount值減少到0,那么就會從內存中刪除該zval: ~~~ $ar=array( 'id'=> 38, 'name'=>'shine', ); $ar['test']=$ar[’name‘]; unset($ar['test'],$ar['name']); xdebug_debug_zval('str'); 結果: ar: (refcount=1, is_ref=0)=array ('id' => (refcount=1, is_ref=0)=38) ~~~ ![](https://img.kancloud.cn/3c/22/3c221841acd7ea78b41d20b947690e7f_644x235.png) 現在,當我們添加一個數組本身作為這個數組的元素時,事情就變得有趣,下個例子將說明這個。例中我們加入了引用操作符,否則php將生成一個復制。 **2.3把數組作為一個元素添加到自己** ~~~ $a?\=?array(?'one'?); $a\[\]?=&?$a; xdebug\_debug\_zval(?'a'?); 結果: a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=... ) ~~~ 能看到數組變量 (a) 同時也是這個數組的第二個元素(1) 指向的變量容器中“refcount”為`2`。上面的輸出結果中的"..."說明發生了遞歸操作, 顯然在這種情況下意味著"..."指向原始數組。 ![](https://img.kancloud.cn/1e/1e/1e1e7fca9f9809e2a60279a894070f87_646x219.png) **2.4unset $a** 現在,我們對$a執行unset操作,這會在symbol table中刪除相應的symbol,同時,zval的refcount減1(之前為2),也就是說,現在的zval應該是這樣的結構: ~~~ (refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=... ) ~~~ ![](https://img.kancloud.cn/73/2f/732fab3ff5134d836f598b06102a2f8a_513x207.png)  這時,不幸的事情發生了!   Unset之后,雖然沒有變量指向該zval,但是該zval卻不能被GC(指PHP5.3之前的單純引用計數機制的GC)清理掉,因為zval的refcount均大于0。這樣,這些zval實際上會一直存在內存中,直到請求結束(參考SAPI的生命周期)。在此之前,這些zval占據的內存不能被使用,便白白浪費了,換句話說,無法釋放的內存導致了內存泄露。   如果這種內存泄露僅僅發生了一次或者少數幾次,倒也還好,但如果是成千上萬次的內存泄露,便是很大的問題了。尤其在長時間運行的腳本中(例如守護程序,一直在后臺執行不會中斷),由于無法回收內存,最終會導致系統“再無內存可用”。 ## 2.寫時copy 前面我們已經介紹過,在變量賦值的過程中例如$b = $a,為了節省空間,并不會為$a和$b都開辟單獨的zval,而是使用共享zval的形式: ![](https://img.kancloud.cn/3c/63/3c6321965808cc3ef60674da2c17080a_445x177.png) ** **2.1如果其中一個變量發生變化時,如何處理zval的共享問題?** ~~~ $a = "a simple test"; $b = $a; echo "before write:".PHP_EOL; xdebug_debug_zval('a'); xdebug_debug_zval('b'); $b = "thss"cho "after write:".PHP_EOL; xdebug_debug_zval('a'); xdebug_debug_zval('b'); 結果: before write: a: (refcount=2, is_ref=0)='a simple test' b: (refcount=2, is_ref=0)='a simple test' after write: a: (refcount=1, is_ref=0)='a simple test' b: (refcount=1, is_ref=0)='thss ~~~ 起初,符號表中a和b指向了同一個zval(這么做的原因是節省內存),而后$b發生了變化,Zend會檢查b指向的zval的refcount是否為1,如果是1,那么說明只有一個符號指向該zval,則直接更改zval。否則,說明這是一個共享的zval,需要將該zval分離出去,以保證單獨變化互不影響,這種機制叫做**COW**–Copy on write。在很多場景下,COW都是一種比較高效的策略。 **2.1那么對于引用變量呢?** ~~~ $a = 'test'; $b = &$a; echo "before change:".PHP\_EOL; xdebug\_debug\_zval('a'); xdebug\_debug\_zval('b'); $b = 12; echo "after change:".PHP\_EOL; xdebug\_debug\_zval('a'); xdebug\_debug\_zval('b'); unset($b); echo "after unset:".PHP\_EOL; xdebug\_debug\_zval('a'); xdebug\_debug\_zval('b'); 結果: before change: a: (refcount=2, is_ref=1)='test' b: (refcount=2, is_ref=1)='test' after change: a: (refcount=2, is_ref=1)=12 b: (refcount=2, is_ref=1)=12 after unset: a: (refcount=1, is_ref=0)=12 ~~~ 可以看出,在改變了$b的值之后,Zend會檢查zval的is\_ref檢查是否是引用變量,如果是引用變量,則直接更改即可,否則,需要執行剛剛提到的zval分離。由于$a 和 $b是引用變量,因而更改共享的zval實際上也間接更改了$a的值。而在unset($b)之后,變量$b從符號表中刪除了。 這里也說明一個問題,unset并不是清除zval,而只是從符號表中刪除相應的symbol。 這一章我們講述了變量引用計數的相關原理,在下一章我們會對PHP的GC機制做一個總結
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看