本文目录导读:

在PHP长驻脚本中,主动释放内存主要依赖以下几个策略:
变量销毁与复用
<?php
// 正确方式:显式销毁大变量
$bigData = fetchLargeData();
processData($bigData);
unset($bigData); // 立即释放
// 复用变量而不是赋值新变量
$buffer = '';
for ($i = 0; $i < 1000; $i++) {
$buffer .= getData(); // 累积使用
if (strlen($buffer) > 1_000_000) {
processBuffer($buffer);
$buffer = ''; // 清空被回收
}
}
循环引用处理
<?php
// 手动解除循环引用
function createCircularReference() {
$a = new stdClass();
$b = new stdClass();
$a->ref = $b;
$b->ref = $a;
// 处理完数据后
$a->ref = null; // 解除引用
$b->ref = null;
// 或者 unset($a, $b);
}
// 使用 weak references (PHP 7.4+)
$object = new stdClass();
$weakRef = WeakReference::create($object);
unset($object); // 对象可被回收
内存缓存管理
<?php
class MemoryManager {
private array $cache = [];
private int $maxSize;
private int $currentSize = 0;
public function __construct(int $maxSizeMB = 50) {
$this->maxSize = $maxSizeMB * 1024 * 1024;
}
public function set(string $key, string $value): void {
$size = strlen($value);
// 检查内存阈值
if ($this->currentSize + $size > $this->maxSize) {
$this->evict(0.2); // 清除20%缓存
}
$this->cache[$key] = $value;
$this->currentSize += $size;
}
public function evict(float $percentage): void {
$removeCount = (int)(count($this->cache) * $percentage);
$keys = array_keys($this->cache);
for ($i = 0; $i < $removeCount && $i < count($keys); $i++) {
$key = $keys[$i];
$this->currentSize -= strlen($this->cache[$key]);
unset($this->cache[$key]);
}
}
public function clear(): void {
$this->cache = [];
$this->currentSize = 0;
}
}
使用生成器处理大数据
<?php
// 不使用生成器(会占用大量内存)
function getLargeData(): array {
$data = [];
for ($i = 0; $i < 1000000; $i++) {
$data[] = processItem($i);
}
return $data;
}
// 使用生成器(逐项处理,内存友好)
function getLargeDataGenerator(): Generator {
for ($i = 0; $i < 1000000; $i++) {
yield processItem($i);
}
}
// 使用方式
foreach (getLargeDataGenerator() as $item) {
processItem($item);
}
垃圾回收主动控制
<?php
class GarbageCollector {
private int $processedCount = 0;
private int $collectInterval = 100;
public function afterProcess(): void {
$this->processedCount++;
if ($this->processedCount % $this->collectInterval === 0) {
// 主动触发垃圾回收
if (gc_enabled()) {
$collected = gc_collect_cycles();
$this->log("Collected $collected cycles");
}
// 重置计数器
$this->processedCount = 0;
}
}
// 内存阈值触发回收
public function checkThreshold(int $thresholdMB = 100): void {
$memoryUsage = memory_get_usage(true);
if ($memoryUsage > $thresholdMB * 1024 * 1024) {
$collected = gc_collect_cycles();
$this->log("Memory above threshold, collected $collected cycles");
}
}
private function log(string $message): void {
echo date('Y-m-d H:i:s') . " - $message\n";
}
}
完整的长驻脚本示例
<?php
class LongRunningProcess {
private PDO $db;
private GarbageCollector $gc;
private MemoryManager $cache;
private int $maxMemoryMB = 100;
public function __construct() {
$this->db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->gc = new GarbageCollector();
$this->cache = new MemoryManager(30);
// 关闭默认的垃圾回收阈值检查
gc_disable();
}
public function run(): void {
while (true) {
try {
$this->processBatch();
$this->gc->afterProcess();
$this->checkAndReleaseMemory();
$this->reconnectDatabase();
} catch (Exception $e) {
$this->handleError($e);
}
// 短暂休眠避免CPU 100%
usleep(100000); // 100ms
}
}
private function processBatch(): void {
// 使用语句预编译和逐行fetch
$stmt = $this->db->prepare("SELECT * FROM large_table WHERE processed = 0 LIMIT 100");
$stmt->execute();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
// 处理单行数据
$result = $this->processRow($row);
// 缓存必要结果时限制大小
if (strlen($result) < 1024 * 100) { // <100KB
$this->cache->set($row['id'], $result);
}
// 立即释放行数据
unset($row);
unset($result);
}
// 释放语句资源
$stmt->closeCursor();
unset($stmt);
}
private function checkAndReleaseMemory(): void {
$currentMemory = memory_get_usage(true);
if ($currentMemory > $this->maxMemoryMB * 1024 * 1024) {
// 清理缓存
$this->cache->evict(0.5);
// 强制垃圾回收
gc_collect_cycles();
// 如果有APCu等缓存,可以清理部分
// apcu_clear_cache();
echo sprintf(
"Memory reclaimed: %s -> %s\n",
$this->formatBytes($currentMemory),
$this->formatBytes(memory_get_usage(true))
);
}
}
private function reconnectDatabase(): void {
// 定期重连避免连接超时
static $lastReconnect = 0;
if (time() - $lastReconnect > 300) { // 每5分钟重连
$this->db = null;
$this->db = new PDO(/* 连接参数 */);
$lastReconnect = time();
}
}
private function handleError(Exception $e): void {
echo "Error: " . $e->getMessage() . "\n";
// 确保资源释放
gc_collect_cycles();
}
private function formatBytes(int $bytes): string {
return round($bytes / 1024 / 1024, 2) . 'MB';
}
}
// 启动脚本
$process = new LongRunningProcess();
$process->run();
监控和调试工具
<?php
// 内存监控封装
class MemoryMonitor {
private array $checkpoints = [];
public function checkpoint(string $label): void {
$this->checkpoints[] = [
'label' => $label,
'time' => microtime(true),
'memory' => memory_get_usage(true),
'peak' => memory_get_peak_usage(true),
];
}
public function report(): void {
foreach ($this->checkpoints as $cp) {
echo sprintf(
"[%s] Memory: %s (Peak: %s)\n",
$cp['label'],
$this->formatBytes($cp['memory']),
$this->formatBytes($cp['peak'])
);
}
}
private function formatBytes(int $bytes): string {
return round($bytes / 1024 / 1024, 2) . 'MB';
}
}
// 使用方式
$monitor = new MemoryMonitor();
$monitor->checkpoint('start');
// ... 处理逻辑 ...
$monitor->checkpoint('after processing');
$monitor->report();
关键原则
- 及时释放引用:使用
unset()释放大对象 - 避免创建长期引用:特别是对象间的循环引用
- 使用生成器:处理大数据集时逐项处理
- 分批次处理:避免一次性加载过多数据
- 手动控制GC:在适当时候调用
gc_collect_cycles() - 设置内存限制:使用
memory_limit和内存检查 - 监控内存变化:定期记录和分析内存使用情况
通过这些策略结合使用,可以有效管理PHP长驻脚本的内存使用,避免内存泄漏和性能下降。