PHP项目长驻脚本如何主动释放内存

wen PHP项目 25

本文目录导读:

PHP项目长驻脚本如何主动释放内存

  1. 变量销毁与复用
  2. 循环引用处理
  3. 内存缓存管理
  4. 使用生成器处理大数据
  5. 垃圾回收主动控制
  6. 完整的长驻脚本示例
  7. 监控和调试工具
  8. 关键原则

在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();

关键原则

  1. 及时释放引用:使用 unset() 释放大对象
  2. 避免创建长期引用:特别是对象间的循环引用
  3. 使用生成器:处理大数据集时逐项处理
  4. 分批次处理:避免一次性加载过多数据
  5. 手动控制GC:在适当时候调用 gc_collect_cycles()
  6. 设置内存限制:使用 memory_limit 和内存检查
  7. 监控内存变化:定期记录和分析内存使用情况

通过这些策略结合使用,可以有效管理PHP长驻脚本的内存使用,避免内存泄漏和性能下降。

抱歉,评论功能暂时关闭!