天天看點

四十三、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不需要顯式的對記憶體進行管理,這些工作都由PHP解釋器進行了。由此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");

?>

既然我們能動态的調整最大的記憶體占用,那我們是否有辦法擷取目前的記憶體占用情況呢?答案是肯定的。

  1. memory_get_usage(),這個函數的作用是擷取 目前PHP腳本所用的記憶體大小。
  2. memory_get_peak_usage(),這個函數的作用傳回 目前腳本到目前位置所占用的記憶體峰值,這樣就可能擷取到目前的腳本的記憶體需求情況。