本文目錄一覽:
php curl 是否需要主動關閉回收資源
可以不用,因為php頁面執行完後會自動回收釋放資源。
如果是命令行運行的程序,需要長時間運行的話,建議主動關閉鏈接,減少資源佔用。
從開發規範和習慣上來說,不需要的資源最好養成用完關閉或消除的習慣。
怎麼用php編寫一個像windows的回收站
刪除的文件設置狀態,比如0是正常的,1是放入回收站的。回收站只顯示狀態為1的文件。如果恢復文件,把1改為0就行了。
php有垃圾回收機制嗎
析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行(php手冊 語言參考-類與對象-構造函數和析構函數)。利用這一點,就可以知道php什麼時候執行了垃圾回收。通常垃圾回收都是在程序結束階段,但是如果讓系統知道這個變數以後不會使用了,php就會啟動垃圾回收的。
PHP命令執行PHP腳本,結束之前,內存會回收嗎
再詳細說下問題:
unix下,用php命令來執行php腳本,在php結束之前,內存有沒有機會被回收?新的GC演算法有沒有機會被調用?
出現這個問題,是因為線上有個 離線數據導入腳本,需要把幾千萬行數據篩選入庫,發現,在執行過程中,到達一定程度,就會拋出 內存使用超過最大值。
1 Fatal error: Allowed memory size of 293601280 bytes exhausted
那第一想到的就是程序是不是有什麼bug,造成內存超出,看了半天沒有發現問題,於是,突然出現了這個疑問。 那要解決這個疑問,最好的辦法就去翻源碼吧。
在之前我這麼說:
都知道,PHP5.3有了新的垃圾回收機制:GC,它不是重點,不談它的原理。
經過翻閱PHP源碼,發現,調用這個的時機是在 main/main.c ::php_request_shutdown這個函數中,
12 /* 7. Shutdown scanner/executor/compiler and restore ini entries */ zend_deactivate(TSRMLS_C);
php_request_shutdown,通過名字就能看出,它是在php請求結束的時候執行的,在這裡會執行 gc_collect_cycles 來清理內存。
其實這句話是沒錯,但它只針對於SAPI介面(之前我就錯在這個地方。),在用PHP命令執行php腳本的時候,是不會執行這個php_request_shutdown的。
那回到最初的問題,過程中到底有沒有執行GC呢?
為了更直觀有效的知道答案,我選擇了最BT,最暴力的方法,攔截gc_collect_cycles,輸出error_log到文件,只要執行了,
那肯定會輸出log來。
重新編譯PHP後,果不其然,符合官方的說法,只要buckets滿超過默認值1000,就會啟動GC來清理沒用的內存,防止內存泄露。
那問 「什麼時間 觸發的GC呢?」,答 「buckets超過1000的時候啊」,這不屁話嘛,要的是真真正正的執行流程,so。。不斷的debug,
不斷的grep,不斷的step,不斷的C+T,終於搞清楚了。下面就來根據官方的說法詳細談談,PHP到底是怎麼觸發的。
有一點要注意,PHP的命令入口 和 sapi介面的入口 是不同的,我就載在這個地方,以為都公用一個。
測試代碼以官方文檔為例:
1234567891011121314 ?phpclass Foo{ public $var = ‘3.1415962654’;} for ( $i = 0; $i = 1000000; $i++ ){ $a = new Foo; $a-self = $a;} echo memory_get_peak_usage(), “\n”;?
這樣的代碼,在PHP5.3之前,肯定會造成大量的 內存泄露,不過,誰在開發時又能開發出這麼變態的代碼來?除非這個人很變態。^.*
那PHP的命令入口是什麼?流程又是什麼?
主要函數流程如下:
入口main函數(sapi/cli/php_cli.c) ==》php_execute_script(main/main.c)==zend_execute_scripts(Zend/zend.c)==execute(Zend/zend_vm_execute.h)
調用GC的地方在execute里。
簡單描述下這個過程,
main 是入口,它的作用是根據我們傳遞的參數做不同的設置,最後會把我們的php腳本作為一個zend_file_handle指針傳遞給
php_execute_script函數,zend_file_handle其實就是把FILE*做了一下封裝,保存了一些其他的文件信息。
php_execute_script會做一些文件檢查工作,把php腳本加到 哈希表included_files中。
php_execute_scripts 會執行 zend_compile_file函數來解釋我們寫的PHP代碼,最後執行execute。
應該都知道 Zend把腳本解析完會生成op代碼保存到 哈希表:active_op_array中,execute會逐個執行每個op,
op基本上都對應一個ZEND_ASSIGN_*_HANDLER這樣的一個宏,它就保存在active_op_array-opline-handlers中。
在進入到 execute之後:
首先初始化execute_data,它保存了很多重要信息,上下文信息,然後調用 ZEND_VM_SET_OPCODE宏,
把execute_data-opline的指針指向active_op_array-opline-handlers。
之後,execute會執行一個while循環,逐條執行opline:
123456789101112131415161718192021222324 while (1) { int ret;#ifdef ZEND_WIN32 if (EG(timed_out)) { zend_timeout(0); }#endif if ((ret = EX(opline)-handler(execute_data TSRMLS_CC)) 0) { switch (ret) { case 1: EG(in_execution) = original_in_execution; return; case 2: op_array = EG(active_op_array); goto zend_vm_enter; case 3: execute_data = EG(current_execute_data); default: break; } } }
每個handlers都會執行一個宏:ZEND_VM_NEXT_OPCODE(),它意思就是跳到下一個Opline,這樣就能逐條執行了。
最後跟蹤 上面的PHP代碼會執行 ZEND_ASSIGN_SPEC_CV_VAR_HANDLER這個宏,它是幹嘛的?他就是 變數賦值
下面代碼執行的操作:
1234 class A{ }$a=new A();
這裡就會執行 這個宏。
在這個宏里有段代碼:
12345678910111213141516171819202122232425262728293031 static int ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS){ zend_op *opline = EX(opline); zend_free_op free_op2; zval *value = _get_zval_ptr_var(opline-op2, EX(Ts), free_op2 TSRMLS_CC); zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(opline-op1, EX(Ts), BP_VAR_W TSRMLS_CC); if (IS_CV == IS_VAR !variable_ptr_ptr) { if (zend_assign_to_string_offset(EX_T(opline-op1.u.var), value, IS_VAR TSRMLS_CC)) { if (!RETURN_VALUE_UNUSED(opline-result)) { EX_T(opline-result.u.var).var.ptr_ptr = EX_T(opline-result.u.var).var.ptr; ALLOC_ZVAL(EX_T(opline-result.u.var).var.ptr); INIT_PZVAL(EX_T(opline-result.u.var).var.ptr); ZVAL_STRINGL(EX_T(opline-result.u.var).var.ptr, Z_STRVAL_P(EX_T(opline-op1.u.var).str_offset.str)+EX_T(opline-op1.u.var).str_offset.offset, 1, 1); } } else if (!RETURN_VALUE_UNUSED(opline-result)) { AI_SET_PTR(EX_T(opline-result.u.var).var, EG(uninitialized_zval_ptr)); PZVAL_LOCK(EG(uninitialized_zval_ptr)); } } else { value = zend_assign_to_variable(variable_ptr_ptr, value, 0 TSRMLS_CC); if (!RETURN_VALUE_UNUSED(opline-result)) { AI_SET_PTR(EX_T(opline-result.u.var).var, value); PZVAL_LOCK(value); } } /* zend_assign_to_variable() always takes care of op2, never free it! */ if (free_op2.var) {zval_ptr_dtor(free_op2.var);}; ZEND_VM_NEXT_OPCODE();}
free_op2.var保存的是 new A的對象.
free_op2.var這個是哪兒來的呢?
在整個execute期間,維持一個 execute_data結構,裡面有個 Ts指針
1 union _temp_variable *Ts;
它用來保存一些臨時的變數信息,比如 new A(),這個會保存到Ts鏈表裡,
opline-op2.u.var這個裡面保存了此臨時變數所在的位置,然後Ts+這個值是一個zval*指針,它就保存了new A產生的對象.
在代碼中
1 if (free_op2.var) {zval_ptr_dtor(free_op2.var);};
zval_ptr_dtor會根據free_op2.var的值執行到 Zend/zend_execute_API.c::_zval_ptr_dtor函數中,
1234567891011121314151617181920212223242526 ZEND_API void _zval_ptr_dtor(zval **zval_ptr ZEND_FILE_LINE_DC) /* {{{ */{ zval *zv = *zval_ptr; #if DEBUG_ZEND=2 printf(“Reducing refcount for %x (%x): %d-%d\n”, *zval_ptr, zval_ptr, Z_REFCOUNT_PP(zval_ptr), Z_REFCOUNT_PP(zval_ptr) – 1);#endif Z_DELREF_P(zv); if (Z_REFCOUNT_P(zv) == 0) { TSRMLS_FETCH(); if (zv != EG(uninitialized_zval)) { GC_REMOVE_ZVAL_FROM_BUFFER(zv); zval_dtor(zv); efree_rel(zv); } } else { TSRMLS_FETCH(); if (Z_REFCOUNT_P(zv) == 1) { Z_UNSET_ISREF_P(zv); } GC_ZVAL_CHECK_POSSIBLE_ROOT(zv); }}
GC_ZVAL_CHECK_POSSIBLE_ROOT(zv);
它就是最終GC演算法執行的地方.
gc_collect_cycles就在這個宏中執行了..
所以..
回到上面的問題,
php無論在SAPI介面或命令端,都會執行 GC演算法來進行垃圾內存回收.
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/159728.html