內存溢出 (OOM) 是 PHP 服務穩定性頭號殺手。PHP 提供內建函數實時獲取內存佔用,結合 Xdebug/Blackfire 等工具,能快速定位洩漏、優化峰值。本文匯總實戰技巧。
| 函數 | 返回值 | 說明 |
|---|---|---|
memory_get_usage(bool $real = false) | int (bytes) | 當前腳本分配的內存。$real=true 返回系統實際分配 (含內存管理器開銷), false (默認) 返回 `emalloc` 管理的內存。 |
memory_get_peak_usage(bool $real = false) | int (bytes) | 腳本啟動以來的峰值內存。定位 OOM 關鍵指標。 |
gc_mem_caches() | int | PHP 7.2+ 強制回收 Zend 內存管理器緩存, 返回釋放字節數。 |
// 開發環境調試用
function mem_usage(string $label = '') {
$mem = memory_get_usage(true) / 1024 / 1024;
$peak = memory_get_peak_usage(true) / 1024 / 1024;
echo sprintf("[MEM] %s: 當前 %.2f MB | 峰值 %.2f MB\n", $label, $mem, $peak);
}
mem_usage('啟動');
// 147296 bytes ≈ 0.14 MB
$bigArr = range(1, 100000); // 大數組
mem_usage('創建 10 萬元素數組');
// 當前 ~13 MB | 峰值 ~13 MB
unset($bigArr);
mem_usage('銷毀數組');
// 當前 ~0.14 MB | 峰值 ~13 MB (峰值不變)
// 手動觸發 GC 釋放緩存 (PHP 7.2+)
gc_mem_caches();
mem_usage('GC 後');
// 當前可能略降, 峰值不變
// 循環處理大數據
foreach ($hugeList as $item) {
process($item);
// 如果 $item 很大且後續不再用
unset($item); // 釋放當前迭代變量引用
}
// 循環結束後
unset($hugeList); // 釋放整個列表引用
// 錯誤: 一次性載入 100 萬行文件到內存
$lines = file('huge.log'); // 可能 OOM
foreach ($lines as $line) { ... }
// 正確: 生成器逐行讀取, 內存常數級
function readLines($file) {
$fp = fopen($file, 'r');
while (($line = fgets($fp)) !== false) {
yield $line;
}
fclose($fp);
}
foreach (readLines('huge.log') as $line) { ... } // 內存極低
// PDO: 非緩衝查詢 (MySQL 需設置 MYSQL_ATTR_USE_BUFFERED_QUERY => false)
$stmt = $pdo->query("SELECT * FROM huge_table", PDO::FETCH_ASSOC);
while ($row = $stmt->fetch()) { process($row); } // 不一次性載入全部結果集
// Eloquent (Laravel): chunkById / cursor
User::chunkById(1000, function ($users) { foreach ($users as $u) { ... } });
// 或
foreach (User::cursor() as $user) { ... } // 生成器, 單條獲取
$res = mysqli_query($link, $sql); // ... 使用結果 mysqli_free_result($res); // 釋放結果集內存 (mysqli) $stmt = $pdo->prepare($sql); $stmt->execute(); // ... $stmt = null; // 關閉語句句柄, 釋放驅動層緩存
class Node {
public $parent;
public $children = [];
}
$a = new Node();
$b = new Node();
$a->children[] = $b;
$b->parent = $a; // 循環引用
// 雖然 PHP 有 GC 會定期清理, 但大量對象圖會延遲回收
// 解決: 手動打破引用或使用弱引用
$b->parent = null; // 打破循環
// 或 PHP 7.4+ WeakReference
$b->parent = WeakReference::create($a);
memory_limit = 256M # 腳本最大內存, 根據業務調整, -1 為無限 (危險) ; 實際生效受限於系統 ulimit -v 和容器 cgroup memory.limit_in_bytes
內存優化無銀彈,核心是測量 → 定位 → 重構 → 驗證閉環。將 `memory_get_peak_usage` 納入 CI/CD 性能基線測試,防範回歸。