1. 程式人生 > >php原始碼之路第六章第一節 (記憶體管理概述)

php原始碼之路第六章第一節 (記憶體管理概述)

記憶體是計算機非常關鍵的部件之一,是暫時儲存程式以及資料的空間,CPU只有有限的暫存器可以用於儲存計算資料,而大部分的資料都是儲存在記憶體中的,程式執行都是在記憶體中進行的。和CPU計算能力一樣,記憶體也是決定計算效率的一個關鍵部分。

計算中的資源中主要包含:CPU計算能力,記憶體資源以及I/O。現代計算機為了充分利用資源,而出現了多工作業系統,通過程序排程來共享CPU計算資源,通過虛擬儲存來分享記憶體儲存能力。本章的記憶體管理中不會介紹作業系統級別的虛擬儲存技術,而是關注在應用層面:如何高效的利用有限的記憶體資源。

目前除了使用C/C++等這類的低層程式語言以外,很多程式語言都將記憶體管理移到了語言之後,例如Java, 各種指令碼語言:PHP/Python/Ruby等等,程式手動維護記憶體的成本非常大,而這些指令碼語言或新型語言都專注於特定領域,這樣能將程式設計師從記憶體管理中解放出來專注於業務的實現。雖然程式設計師不需要手動維護記憶體,而在程式執行過程中記憶體的使用還是要進行管理的,記憶體管理的工作也就程式語言實現程式設計師的工作了。

記憶體管理的主要工作是儘可能高效的利用記憶體。

記憶體的使用操作包括申請記憶體,銷燬記憶體,修改記憶體的大小等。如果申請了記憶體在使用完後沒有及時釋放則可能會造成記憶體洩露,如果這種情況出現在常駐程式中,久而久之,程式會把機器的記憶體耗光。所以對於類似於PHP這樣沒有低層記憶體管理的語言來說,記憶體管理是其至關重要的一個模組,它在很大程式上決定了程式的執行效率,

在PHP層面來看,定義的變數、類、函式等等實體在執行過程中都會涉及到記憶體的申請和釋放,例如變數可能會在超出作用域後會進行銷燬,在計算過程中會產生的臨時資料等都會有記憶體操作,像類物件,函式定義等資料則會在請求結束之後才會被釋放。在這過程中合適申請記憶體合適釋放記憶體就比較關鍵了。 PHP從開始就有一套屬於自己的記憶體管理機制,在5.3之前使用的是經典的引用計數技術,但引用技術存在一定的技術缺陷,在PHP5.3之後,引入了新的垃圾回收機制,至此,PHP的記憶體管理機制更加完善。

從某個意義上講,資源總是有限的,計算機資源也是如此,衡量一個計算機處理能力的指標有很多,根據不同的應用需要也會有不同的指標,比如3D遊戲對顯示卡的效能有要求,而Web伺服器對吞吐量及響應時間有要求,通常CPU、記憶體及硬碟的讀取和計算速度具有決定性的作用,在同一時刻這些資源是有限的,正是因為有限我們才需要合理的利用他們。

作業系統的記憶體管理
當計算機的電源被開啟之後,不管你使用的是什麼作業系統,這些軟體可能已經在使用記憶體了。這是由計算機的結構決定的,作業系統也是一種軟體,只不過它是比較特殊的軟體,管理計算機的所有資源,普通應用程式和作業系統的關係有點像老師和學生,老師通常管理一切,而學生的行為會受到老師或學校規定的限制,例如普通應用程式無法直接訪問實體記憶體或者其他硬體資源。

作業系統直接管理著記憶體,所以作業系統也需要進行記憶體管理,記憶體管理是如此之重要,計算機中通常都有記憶體管理單元(MMU) 用於處理CPU對記憶體的訪問。

應用層的記憶體管理

由於計算機的記憶體由作業系統進行管理,所以普通應用程式是無法直接對記憶體進行訪問的,應用程式只能向作業系統申請記憶體,通常的應用也是這麼做的,在需要的時候通過類似malloc之類的庫函式向作業系統申請記憶體,在一些對效能要求較高的應用場景下是需要頻繁的使用和釋放記憶體的,比如Web伺服器,程式語言等,由於向作業系統申請記憶體空間會引發系統呼叫,系統呼叫和普通的應用層函式呼叫效能差別非常大。

因為系統呼叫會將CPU從使用者態切換到核心,因為涉及到實體記憶體的操作,只有作業系統才能進行,而這種切換的成本是非常大的,如果頻繁的在核心態和使用者態之間切換會產生效能問題。

鑑於系統呼叫的開銷,一些對效能有要求的應用通常會自己在使用者態進行記憶體管理,例如第一次申請稍大的記憶體留著備用,而使用完釋放的記憶體並不是馬上歸還給作業系統,可以將記憶體進行復用,這樣可以避免多次的記憶體申請和釋放所帶來的效能消耗。

    PHP不需要顯式的對記憶體進行管理,這些工作都由Zend引擎進行管理了。PHP內部有一個記憶體管理體系,它會自動將不再使用的記憶體垃圾進行釋放,這部分的內容後面的小節會介紹到。

PHP中記憶體相關的功能特性

可能有很多的人碰到過類似下面的錯誤吧:
Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)
    這個錯誤的資訊很明確,PHP已經達到了允許使用的最大記憶體了,通常上來說這很有可能是我們的程式編寫的有些問題。比如:一次性讀取超大的檔案到記憶體中,或者出現超大的陣列,或者在大迴圈中的沒有及時是放掉不再使用的變數,這些都有可能會造成記憶體佔用過大而被終止。

    PHP預設的最大記憶體使用大小是32M, 如果你真的需要使用超過32M的記憶體可以修改php.ini配置檔案的如下配置:
memory_limit = 32M
    如果你無法修改php配置檔案,如果你的PHP環境沒有禁用ini_set()函式,也可以動態的修改最大的記憶體佔用大小:
<?php
ini_set("memory_limit", "128M");
    既然我們能動態的調整最大的記憶體佔用,那我們是否有辦法獲取目前的記憶體佔用情況呢?答案是肯定的。

    memory_get_usage(),這個函式的作用是獲取目前PHP指令碼所用的記憶體大小。 
    memory_get_peak_usage(),這個函式的作用返回當前指令碼到目前位置所佔用的記憶體峰值,這樣就可能獲取到目前的指令碼的記憶體需求情況。 
單就PHP使用者空間提供的功能來說,我們似乎無法控制記憶體的使用,只能被動的獲取記憶體的佔用情況,這樣的話我們學習記憶體管理有什麼用呢?

    前面介紹到引用計數,函式表,符號表,常量表等。當我們明白這些資訊都會佔用記憶體的時候,我們可以有意的避免不必要的浪費記憶體,比如我們在專案中通常會使用autoload來避免一次性把不一定會使用的類包含進來,而這些資訊是會佔用記憶體的,如果我們及時把不再使用的變數unset掉之後可能會釋放掉它所佔用的空間,

    前面之所以會說把變數unset掉時候可能會把它釋放掉的原因是: 在PHP中為了避免不必要的記憶體複製,採用了引用計數和寫時複製的技術, 所以這裡unset只是將引用關係打破,如果還有其他變數指向該記憶體, 它所佔用的記憶體還是不會被釋放的。 
    當然這還有一種情況:出現迴圈引用,這個就得靠gc來處理了, 記憶體不會當時就是放,只有在gc環節才會被釋放。

    後面的章節主要介紹PHP在執行時的記憶體使用和管理細節。這也能幫助我們寫出更為記憶體友好的PHP程式碼。