欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長資訊網
          最全最豐富的資訊網站

          探討php的垃圾回收機制

          探討php的垃圾回收機制

          在平時php-fpm的時候,可能很少人注意php的變量回收,但是到swoole常駐內存開發(fā)后,就不得不重視這個了,因為在常駐內存下,如果不了解變量回收機制,可能就會出現(xiàn)內存泄露的問題,本文將一步步帶你了解php的垃圾回收機制,讓你寫出的代碼不再內存泄漏

          寫時復制

          首先,php的變量復制用的是寫時復制方式,舉個例子.

          $a='仙士可'.time(); $b=$a; $c=$a; //這個時候內存占用相同,$b,$c都將指向$a的內存,無需額外占用   $b='仙士可1號'; //這個時候$b的數(shù)據(jù)已經改變了,無法再引用$a的內存,所以需要額外給$b開拓內存空間   $a='仙士可2號'; //$a的數(shù)據(jù)發(fā)生了變化,同樣的,$c也無法引用$a了,需要給$a額外開拓內存空間

          詳細寫時復制可查看:php寫時復制

          引用計數(shù)

          既然變量會引用內存,那么刪除變量的時候,就會出現(xiàn)一個問題了:

          $a='仙士可'; $b=$a; $c=$a; //這個時候內存占用相同,$b,$c都將指向$a的內存,無需額外占用   $b='仙士可1號'; //這個時候$b的數(shù)據(jù)已經改變了,無法再引用$a的內存,所以需要額外給$b開拓內存空間   unset($c); //這個時候,刪除$c,由于$c的數(shù)據(jù)是引用$a的數(shù)據(jù),那么直接刪除$a?

          很明顯,當$c引用$a的時候,刪除$c,不能把$a的數(shù)據(jù)直接給刪除,那么該怎么做呢?

          這個時候,php底層就使用到了引用計數(shù)這個概念

          引用計數(shù),給變量引用的次數(shù)進行計算,當計數(shù)不等于0時,說明這個變量已經被引用,不能直接被回收,否則可以直接回收,例如:

          $a = '仙士可'.time(); $b = $a; $c = $a;   xdebug_debug_zval('a'); xdebug_debug_zval('b'); xdebug_debug_zval('c');   $b='仙士可2號'; xdebug_debug_zval('a'); xdebug_debug_zval('b');   echo "腳本結束n";

          將輸出:

          a: (refcount=3, is_ref=0)='仙士可1578154814' b: (refcount=3, is_ref=0)='仙士可1578154814' c: (refcount=3, is_ref=0)='仙士可1578154814' a: (refcount=2, is_ref=0)='仙士可1578154814' b: (refcount=1, is_ref=0)='仙士可2號' 腳本結束

          注意,xdebug_debug_zval函數(shù)是xdebug擴展的,使用前必須安裝xdebug擴展

          引用計數(shù)特殊情況

          當變量值為整型,浮點型時,在賦值變量時,php7底層將會直接把值存儲(php7的結構體將會直接存儲簡單數(shù)據(jù)類型),refcount將為0

          $a = 1111; $b = $a; $c = 22.222; $d = $c;   xdebug_debug_zval('a'); xdebug_debug_zval('b'); xdebug_debug_zval('c'); xdebug_debug_zval('d'); echo "腳本結束n";

          輸出:

          a: (refcount=0, is_ref=0)=1111 b: (refcount=0, is_ref=0)=1111 c: (refcount=0, is_ref=0)=22.222 d: (refcount=0, is_ref=0)=22.222 腳本結束

          當變量值為interned string字符串型(變量名,函數(shù)名,靜態(tài)字符串,類名等)時,變量值存儲在靜態(tài)區(qū),內存回收被系統(tǒng)全局接管,引用計數(shù)將一直為1(php7.3)

          $str = '仙士可';    // 靜態(tài)字符串  $str = '仙士可' . time();//普通字符串  $a = 'aa'; $b = $a; $c = $b;   $d = 'aa'.time(); $e = $d; $f = $d;   xdebug_debug_zval('a'); xdebug_debug_zval('d'); echo "腳本結束n";  輸出:  a: (refcount=1, is_ref=0)='aa' d: (refcount=3, is_ref=0)='aa1578156506' 腳本結束

          當變量值為以上幾種時,復制變量將會直接拷貝變量值,所以將不存在多次引用的情況

          引用時引用計數(shù)變化

          如下代碼:

          $a = 'aa'; $b = &$a; $c = $b;   xdebug_debug_zval('a'); xdebug_debug_zval('b'); xdebug_debug_zval('c'); echo "腳本結束n";

          將輸出:

          a: (refcount=2, is_ref=1)='aa' b: (refcount=2, is_ref=1)='aa' c: (refcount=1, is_ref=0)='aa' 腳本結束

          當引用時,被引用變量的value以及類型將會更改為引用類型,并將引用值指向原來的值內存地址中.

          之后引用變量的類型也會更改為引用類型,并將值指向原來的值內存地址,這個時候,值內存地址被引用了2次,所以refcount=2.

          而$c并非是引用變量,所以將值復制給了$c,$c引用還是為1

          詳細引用計數(shù)知識,底層原理可查看:https://www.cnblogs.com/sohuhome/p/9800977.html

          php生命周期

          php將每個運行域作為一次生命周期,每次執(zhí)行完一個域,將回收域內所有相關變量:

          <?php /**  * Created by PhpStorm.  * User: Tioncico  * Date: 2020/1/6 0006  * Time: 14:22  */   echo "php文件的全局開始n";   class A{     protected $a;     function __construct($a)     {         $this->a = $a;         echo "類A{$this->a}生命周期開始n";     }     function test(){         echo "類test方法域開始n";         echo "類test方法域結束n";     } //通過類析構函數(shù)的特性,當類初始化或回收時,會調用相應的方法     function __destruct()     {         echo "類A{$this->a}生命周期結束n";         // TODO: Implement __destruct() method.     } }   function a1(){     echo "a1函數(shù)域開始n";     $a = new A(1);     echo "a1函數(shù)域結束n";     //函數(shù)結束,將回收所有在函數(shù)a1的變量$a } a1();   $a = new A(2);   echo "php文件的全局結束n"; //全局結束后,會回收全局的變量$a

          可看出,每個方法/函數(shù)都作為一個作用域,當運行完該作用域時,將會回收這里面的所有變量.

          再看看這個例子:

          echo "php文件的全局開始n";   class A {     protected $a;       function __construct($a)     {         $this->a = $a;         echo "類{$this->a}生命周期開始n";     }       function test()     {         echo "類test方法域開始n";         echo "類test方法域結束n";     }   //通過類析構函數(shù)的特性,當類初始化或回收時,會調用相應的方法     function __destruct()     {         echo "類{$this->a}生命周期結束n";         // TODO: Implement __destruct() method.     } }   $arr = []; $i = 0; while (1) {     $arr[] = new A('arr_' . $i);     $obj = new A('obj_' . $i);     $i++;     echo "數(shù)組大小:". count($arr).'n';     sleep(1); //$arr 會隨著循環(huán),慢慢的變大,直到內存溢出   }   echo "php文件的全局結束n"; //全局結束后,會回收全局的變量$a

          全局變量只有在腳本結束后才會回收,而在這份代碼中,腳本永遠不會被結束,也就說明變量永遠不會回收,$arr還在不斷的增加變量,直到內存溢出.

          內存泄漏

          請看代碼:

          function a(){     class A {         public $ref;         public $name;           public function __construct($name) {             $this->name = $name;             echo($this->name.'->__construct();'.PHP_EOL);         }           public function __destruct() {             echo($this->name.'->__destruct();'.PHP_EOL);         }     }       $a1 = new A('$a1');     $a2 = new A('$a2');     $a3 = new A('$3');       $a1->ref = $a2;     $a2->ref = $a1;       unset($a1);     unset($a2);       echo('exit(1);'.PHP_EOL); } a(); echo('exit(2);'.PHP_EOL);

          當$a1和$a2的屬性互相引用時,unset($a1,$a2) 只能刪除變量的引用,卻沒有真正的刪除類的變量,這是為什么呢?

          首先,類的實例化變量分為2個步驟,1:開辟類存儲空間,用于存儲類數(shù)據(jù),2:實例化一個變量,類型為class,值指向類存儲空間.

          當給變量賦值成功后,類的引用計數(shù)為1,同時,a1->ref指向了a2,導致a2類引用計數(shù)增加1,同時a1類被a2->ref引用,a1引用計數(shù)增加1

          當unset時,只會刪除類的變量引用,也就是-1,但是該類其實還存在了一次引用(類的互相引用),

          這將造成這2個類內存永遠無法釋放,直到被gc機制循環(huán)查找回收,或腳本終止回收(域結束無法回收).

          手動回收機制

          在上面,我們知道了腳本回收,域結束回收2種php回收方式,那么可以手動回收嗎?答案是可以的.

          手動回收有以下幾種方式:

          unset,賦值為null,變量賦值覆蓋,gc_collect_cycles函數(shù)回收

          unset

          unset為最常用的一種回收方式,例如:

          class A {     public $ref;     public $name;       public function __construct($name)     {         $this->name = $name;         echo($this->name . '->__construct();' . PHP_EOL);     }       public function __destruct()     {         echo($this->name . '->__destruct();' . PHP_EOL);     } }   $a = new A('$a'); $b = new A('$b'); unset($a); //a將會先回收 echo('exit(1);' . PHP_EOL); //b需要腳本結束才會回收

          輸出:

          $a->__construct(); $b->__construct(); $a->__destruct(); exit(1); $b->__destruct();

          unset的回收原理其實就是引用計數(shù)-1,當引用計數(shù)-1之后為0時,將會直接回收該變量,否則不做操作(這就是上面內存泄漏的原因,引用計數(shù)-1并沒有等于0)

          =null回收

          class A {     public $ref;     public $name;       public function __construct($name)     {         $this->name = $name;         echo($this->name . '->__construct();' . PHP_EOL);     }       public function __destruct()     {         echo($this->name . '->__destruct();' . PHP_EOL);     } }   $a = new A('$a'); $b = new A('$b'); $c = new A('$c'); unset($a); $c=null; xdebug_debug_zval('a'); xdebug_debug_zval('b'); xdebug_debug_zval('c');   echo('exit(1);' . PHP_EOL);

          =null和unset($a),作用其實都為一致,null將變量值賦值為null,原先的變量值引用計數(shù)-1,而unset是將變量名從php底層變量表中清理,并將變量值引用計數(shù)-1,唯一的區(qū)別在于,=null,變量名還存在,而unset之后,該變量就沒了:

          $a->__construct(); $b->__construct(); $c->__construct(); $a->__destruct(); $c->__destruct(); a: no such symbol //$a已經不在符號表 b: (refcount=1, is_ref=0)=class A { public $ref = (refcount=0, is_ref=0)=NULL; public $name = (refcount=1, is_ref=0)='$b' } c: (refcount=0, is_ref=0)=NULL  //c還存在,只是值為null exit(1); $b->__destruct();

          變量覆蓋回收

          通過給變量賦值其他值(例如null)進行回收:

          class A {     public $ref;     public $name;       public function __construct($name)     {         $this->name = $name;         echo($this->name . '->__construct();' . PHP_EOL);     }       public function __destruct()     {         echo($this->name . '->__destruct();' . PHP_EOL);     } }   $a = new A('$a'); $b = new A('$b'); $c = new A('$c'); $a=null; $c= '練習時長兩年半的個人練習生'; xdebug_debug_zval('a'); xdebug_debug_zval('b'); xdebug_debug_zval('c');   echo('exit(1);' . PHP_EOL);

          將輸出:

          $a->__construct(); $b->__construct(); $c->__construct(); $a->__destruct(); $c->__destruct(); a: (refcount=0, is_ref=0)=NULL b: (refcount=1, is_ref=0)=class A { public $ref = (refcount=0, is_ref=0)=NULL; public $name = (refcount=1, is_ref=0)='$b' } c: (refcount=1, is_ref=0)='練習時長兩年半的個人練習生' exit(1); $b->__destruct();

          可以看出,c由于覆蓋賦值,將原先A類實例的引用計數(shù)-1,導致了$c的回收,但是從程序的內存占用來說,覆蓋變量并不是意義上的內存回收,只是將變量的內存修改為了其他值.內存不會直接清空.

          gc_collect_cycles

          回到之前的內存泄漏章節(jié),當寫程序不小心造成了內存泄漏,內存越來越大,可是php默認只能腳本結束后回收,那該怎么辦呢?我們可以使用gc_collect_cycles 函數(shù),進行手動回收

          function a(){     class A {         public $ref;         public $name;           public function __construct($name) {             $this->name = $name;             echo($this->name.'->__construct();'.PHP_EOL);         }           public function __destruct() {             echo($this->name.'->__destruct();'.PHP_EOL);         }     }       $a1 = new A('$a1');     $a2 = new A('$a2');       $a1->ref = $a2;     $a2->ref = $a1;       $b = new A('$b');     $b->ref = $a1;       echo('$a1 = $a2 = $b = NULL;'.PHP_EOL);     $a1 = $a2 = $b = NULL;     echo('gc_collect_cycles();'.PHP_EOL);     echo('// removed cycles: '.gc_collect_cycles().PHP_EOL);     //這個時候,a1,a2已經被gc_collect_cycles手動回收了     echo('exit(1);'.PHP_EOL);   } a(); echo('exit(2);'.PHP_EOL);

          輸出:

          $a1->__construct(); $a2->__construct(); $b->__construct(); $a1 = $a2 = $b = NULL; $b->__destruct(); gc_collect_cycles(); $a1->__destruct(); $a2->__destruct(); // removed cycles: 4 exit(1); exit(2);

          注意,gc_colect_cycles 函數(shù)會從php的符號表,遍歷所有變量,去實現(xiàn)引用計數(shù)的計算并清理內存,將消耗大量的cpu資源,不建議頻繁使用

          另外,除去這些方法,php內存到達一定臨界值時,會自動調用內存清理(我猜的),每次調用都會消耗大量的資源,可通過gc_disable 函數(shù),去關閉php的自動gc

          推薦教程:《php教程》

          贊(0)
          分享到: 更多 (0)
          網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號