php7內存回收機制,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算法來進行垃圾內存回收.

php有垃圾回收機制嗎

析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行(php手冊 語言參考-類與對象-構造函數和析構函數)。利用這一點,就可以知道php什麼時候執行了垃圾回收。通常垃圾回收都是在程序結束階段,但是如果讓系統知道這個變量以後不會使用了,php就會啟動垃圾回收的。

回收內存什麼意思

你編寫程序,程序的一些變量會申請內存去存儲。比如你在一個函數裡面定義了一個變量(那麼我們通常說這個變量是局部變量,只能在該函數內部使用),程序會將該變量存儲在該函數的堆棧中,那麼在函數執行完之後,堆棧的內容就要清理,這就可以理解為回收內存。如今的java和.net的GC回收機制都很好。不需要程序員做太多的事情。(我們將這些可以自動回收的資源叫做託管資源),其他一些非託管資源,比如數據庫鏈接,文件等等,是需要我們自己去關閉的。

請解釋一下垃圾回收的原理,可以直接從內存中回收嗎

Java 語言中一個顯著的特點就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理 的問題迎刃而解,它使得Java程序員在編寫程序的時候不再需要考慮內存管理。垃圾回收 可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作為一個單獨的低級別的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收,因 為Java語言規範並不保證GC一定會執行。回收機制有分代複製垃圾回收和標記垃圾回收, 增量垃圾回收。

服務器上php網站,訪問剛開始速度很快後來越來越慢,直到無法訪問,重啟下apache就好,然後又越來越慢

apche沒有內置的回收內存垃圾機制 所以會這樣。另外可以考慮不記錄日誌會好一點。

如果需要的話 寫個腳本(winx86 x64平台可以用計劃任務) 定時restart下apache就好了。

另外 apache如果用的 fastcgi 你這個問題基本不存在。

總之就是內存回收機制問題。

原創文章,作者:AICJ,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/136868.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
AICJ的頭像AICJ
上一篇 2024-10-04 00:16
下一篇 2024-10-04 00:16

相關推薦

  • Python創建分配內存的方法

    在python中,我們常常需要創建並分配內存來存儲數據。不同的類型和數據結構可能需要不同的方法來分配內存。本文將從多個方面介紹Python創建分配內存的方法,包括列表、元組、字典、…

    編程 2025-04-29
  • Python變量在內存中的存儲

    該文章將從多個方面對Python變量在內存中的存儲進行詳細闡述,包括變量的聲明和賦值、變量的引用和指向、內存地址的變化、內存管理機制等。 一、聲明和賦值 在Python中,變量聲明…

    編程 2025-04-29
  • Python計算內存佔用

    Python是一種高級的、解釋性的、面向對象的、動態的程序語言,因其易於學習、易於閱讀、可移植性好等優點,越來越受到開發者的青睞。當我們編寫Python代碼時,可能經常需要計算程序…

    編程 2025-04-28
  • 使用Go-Redis獲取Redis集群內存使用率

    本文旨在介紹如何使用Go-Redis獲取Redis集群的內存使用率。 一、Go-Redis簡介 Go-Redis是一個用於連接Redis服務器的Golang客戶端。它支持Redis…

    編程 2025-04-28
  • Spring S_CSRF防護機制實現及應用

    Spring S_CSRF防護機制是Spring Security框架提供的一個針對跨站請求偽造攻擊(CSRF)的保護機制。本文將從以下幾個方面詳細介紹Spring S_CSRF防…

    編程 2025-04-28
  • Python的垃圾回收機制

    本文將對Python的垃圾回收機制進行詳細闡述,着重介紹它的基本原理和實現方式。此外,我們還將介紹常見的問題及解決方法,並給出相應的代碼示例。 一、Python的垃圾回收概述 垃圾…

    編程 2025-04-27
  • Docker 垃圾電腦的解決方案

    Docker 是一種輕量級的容器化技術,可以在一個操作系統中,同時運行多個獨立的應用。在使用 Docker 的過程中,可能會出現 Docker 佔用大量硬盤空間,導致電腦變得極其緩…

    編程 2025-04-27
  • Python內置函數——查看對象內存

    本文將介紹Python內置函數中,在開發中查看對象內存的相關函數。 一、id()函數 id()函數是Python內置函數,用於返回對象的唯一標識符,也就是對象在內存中的地址。 nu…

    編程 2025-04-27
  • 機制與策略分離

    了解機制與策略分離的解決方法與優勢 一、概述 機制與策略分離是一種軟件設計理念,它將複雜的系統、組件等模塊化,通過分離機制與策略,把模塊實現的方式與具體使用方式分開。 機制是實現某…

    編程 2025-04-27
  • Python進程池共享內存用法介紹

    本文將從多個方面詳細闡述Python進程池共享內存的相關知識,包括如何使用進程池、進程池的實現原理、進程池中的共享內存管理等。本文內容將涵蓋: 一、進程池的使用 進程池是一種有效的…

    編程 2025-04-27

發表回復

登錄後才能評論