399 lines
10 KiB
Markdown
399 lines
10 KiB
Markdown
|
|
# P4:内存监控机制详细说明
|
|||
|
|
|
|||
|
|
## 一、核心功能
|
|||
|
|
|
|||
|
|
实时监控JavaScript内存使用情况,在内存压力过大时自动触发清理,防止:
|
|||
|
|
- ❌ 浏览器卡顿、冻结
|
|||
|
|
- ❌ "页面无响应"提示
|
|||
|
|
- ❌ 标签页崩溃(Aw, Snap!)
|
|||
|
|
- ❌ 内存泄漏导致的性能下降
|
|||
|
|
|
|||
|
|
## 二、实现原理
|
|||
|
|
|
|||
|
|
### 1. 浏览器内存API
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
if (performance.memory) {
|
|||
|
|
const memory = {
|
|||
|
|
used: performance.memory.usedJSHeapSize, // 已使用的JS堆(字节)
|
|||
|
|
total: performance.memory.totalJSHeapSize, // 当前分配的JS堆
|
|||
|
|
limit: performance.memory.jsHeapSizeLimit // JS堆上限(通常2GB)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**注意**:
|
|||
|
|
- ✅ Chrome/Edge支持
|
|||
|
|
- ❌ Firefox/Safari不支持(会优雅降级,不影响功能)
|
|||
|
|
|
|||
|
|
### 2. 监控策略
|
|||
|
|
|
|||
|
|
**检查频率**:每30秒一次(不影响性能)
|
|||
|
|
|
|||
|
|
**三级预警机制**:
|
|||
|
|
|
|||
|
|
| 内存使用率 | 级别 | 触发动作 | 影响 |
|
|||
|
|
|-----------|------|---------|------|
|
|||
|
|
| < 70% | 🟢 正常 | 无操作 | 无 |
|
|||
|
|
| 70-85% | 🟡 警告 | 标准清理 | 轻微,几乎无感知 |
|
|||
|
|
| > 85% | 🔴 危险 | 紧急清理 | 中等,可能短暂卡顿 |
|
|||
|
|
|
|||
|
|
### 3. 清理动作详解
|
|||
|
|
|
|||
|
|
#### 🟡 标准清理(70-85%)
|
|||
|
|
|
|||
|
|
**触发条件**:内存使用率超过70%
|
|||
|
|
|
|||
|
|
**清理动作**:
|
|||
|
|
```javascript
|
|||
|
|
// 1. 清理已完成的任务进度数据
|
|||
|
|
forceCleanupTaskProgress()
|
|||
|
|
// - 删除 status='completed' 的进度对象
|
|||
|
|
// - 释放 result 对象引用
|
|||
|
|
|
|||
|
|
// 2. 清理UI更新队列
|
|||
|
|
clearPendingUIUpdates()
|
|||
|
|
// - 清空 pendingUIUpdates Map
|
|||
|
|
// - 取消待处理的定时器
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**预期效果**:
|
|||
|
|
- 释放约 **20-40MB** 内存(100个Token场景)
|
|||
|
|
- 用户**无感知**,不影响任何功能
|
|||
|
|
- 控制台输出警告日志
|
|||
|
|
|
|||
|
|
#### 🔴 紧急清理(>85%)
|
|||
|
|
|
|||
|
|
**触发条件**:内存使用率超过85%
|
|||
|
|
|
|||
|
|
**清理动作**:
|
|||
|
|
```javascript
|
|||
|
|
// 1. 执行标准清理
|
|||
|
|
forceCleanupTaskProgress()
|
|||
|
|
clearPendingUIUpdates()
|
|||
|
|
|
|||
|
|
// 2. 删除所有任务的详细result数据
|
|||
|
|
Object.keys(taskProgress.value).forEach(tokenId => {
|
|||
|
|
const progress = taskProgress.value[tokenId]
|
|||
|
|
if (progress.result) {
|
|||
|
|
delete progress.result // 删除详细结果,保留状态
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 3. 手动触发响应式更新
|
|||
|
|
triggerRef(taskProgress)
|
|||
|
|
|
|||
|
|
// 4. 建议浏览器执行垃圾回收(如果支持)
|
|||
|
|
if (window.gc) window.gc()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**预期效果**:
|
|||
|
|
- 释放约 **100-200MB** 内存(100个Token场景)
|
|||
|
|
- 用户**轻微感知**(可能短暂卡顿0.5-1秒)
|
|||
|
|
- **影响**:任务详情弹窗中的数据会丢失
|
|||
|
|
- 控制台输出错误日志
|
|||
|
|
|
|||
|
|
## 三、完整代码实现
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
/**
|
|||
|
|
* 获取当前内存使用情况(MB)
|
|||
|
|
* @returns {Object|null} { used, total, limit } 或 null(不支持的浏览器)
|
|||
|
|
*/
|
|||
|
|
const getMemoryUsage = () => {
|
|||
|
|
if (!performance.memory) {
|
|||
|
|
return null // Firefox/Safari等浏览器不支持
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
used: Math.round(performance.memory.usedJSHeapSize / 1048576), // MB
|
|||
|
|
total: Math.round(performance.memory.totalJSHeapSize / 1048576), // MB
|
|||
|
|
limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576) // MB
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 监控内存使用,超限时自动清理
|
|||
|
|
*/
|
|||
|
|
const monitorMemoryUsage = () => {
|
|||
|
|
if (!isExecuting.value) return
|
|||
|
|
|
|||
|
|
const memory = getMemoryUsage()
|
|||
|
|
if (!memory) return // 浏览器不支持,直接返回
|
|||
|
|
|
|||
|
|
const usagePercent = (memory.used / memory.limit) * 100
|
|||
|
|
|
|||
|
|
// 🟡 警告级别:内存使用超过70%
|
|||
|
|
if (usagePercent > 70 && usagePercent <= 85) {
|
|||
|
|
console.warn(
|
|||
|
|
`⚠️ [内存监控] 内存使用率: ${usagePercent.toFixed(1)}% ` +
|
|||
|
|
`(${memory.used}MB / ${memory.limit}MB) - 触发标准清理`
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 执行标准清理
|
|||
|
|
forceCleanupTaskProgress()
|
|||
|
|
clearPendingUIUpdates()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 🔴 危险级别:内存使用超过85%
|
|||
|
|
if (usagePercent > 85) {
|
|||
|
|
console.error(
|
|||
|
|
`🚨 [内存监控] 内存使用率危险: ${usagePercent.toFixed(1)}% ` +
|
|||
|
|
`(${memory.used}MB / ${memory.limit}MB) - 触发紧急清理`
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 执行标准清理
|
|||
|
|
forceCleanupTaskProgress()
|
|||
|
|
clearPendingUIUpdates()
|
|||
|
|
|
|||
|
|
// 🔥 紧急措施:删除所有任务的详细result数据
|
|||
|
|
let deletedCount = 0
|
|||
|
|
Object.keys(taskProgress.value).forEach(tokenId => {
|
|||
|
|
const progress = taskProgress.value[tokenId]
|
|||
|
|
if (progress && progress.result) {
|
|||
|
|
delete progress.result
|
|||
|
|
deletedCount++
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (deletedCount > 0) {
|
|||
|
|
triggerRef(taskProgress)
|
|||
|
|
console.error(`🗑️ [紧急清理] 已删除 ${deletedCount} 个Token的详细结果数据`)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 建议垃圾回收(Chrome需要启动参数 --expose-gc)
|
|||
|
|
if (typeof window !== 'undefined' && window.gc) {
|
|||
|
|
window.gc()
|
|||
|
|
console.warn('♻️ [紧急清理] 已触发强制垃圾回收')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 启动内存监控定时器(每30秒检查一次)
|
|||
|
|
*/
|
|||
|
|
let memoryMonitorTimer = null
|
|||
|
|
|
|||
|
|
const startMemoryMonitor = () => {
|
|||
|
|
if (memoryMonitorTimer) return // 已启动,避免重复
|
|||
|
|
|
|||
|
|
// 立即执行一次检查
|
|||
|
|
monitorMemoryUsage()
|
|||
|
|
|
|||
|
|
// 每30秒检查一次
|
|||
|
|
memoryMonitorTimer = setInterval(() => {
|
|||
|
|
monitorMemoryUsage()
|
|||
|
|
}, 30000)
|
|||
|
|
|
|||
|
|
if (logConfig.value.batch) {
|
|||
|
|
console.log('🔄 [内存监控] 已启动(每30秒检查一次)')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 停止内存监控
|
|||
|
|
*/
|
|||
|
|
const stopMemoryMonitor = () => {
|
|||
|
|
if (memoryMonitorTimer) {
|
|||
|
|
clearInterval(memoryMonitorTimer)
|
|||
|
|
memoryMonitorTimer = null
|
|||
|
|
|
|||
|
|
if (logConfig.value.batch) {
|
|||
|
|
console.log('⏹️ [内存监控] 已停止')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 四、集成到批量任务
|
|||
|
|
|
|||
|
|
**在 startBatchExecution 开始时启动**:
|
|||
|
|
```javascript
|
|||
|
|
const startBatchExecution = async (...) => {
|
|||
|
|
// ... 现有代码 ...
|
|||
|
|
|
|||
|
|
// 🔥 启动内存监控
|
|||
|
|
startMemoryMonitor()
|
|||
|
|
|
|||
|
|
// 执行批量任务...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**在 completeBatchExecution 结束时停止**:
|
|||
|
|
```javascript
|
|||
|
|
const completeBatchExecution = () => {
|
|||
|
|
// ... 现有清理代码 ...
|
|||
|
|
|
|||
|
|
// 🔥 停止内存监控
|
|||
|
|
stopMemoryMonitor()
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 五、优缺点分析
|
|||
|
|
|
|||
|
|
### ✅ 优点
|
|||
|
|
|
|||
|
|
1. **自动保护**
|
|||
|
|
- 无需用户干预
|
|||
|
|
- 自动检测并清理
|
|||
|
|
- 防止浏览器崩溃
|
|||
|
|
|
|||
|
|
2. **三级预警**
|
|||
|
|
- 渐进式清理策略
|
|||
|
|
- 最小化对用户体验的影响
|
|||
|
|
- 只在必要时触发紧急清理
|
|||
|
|
|
|||
|
|
3. **性能开销极小**
|
|||
|
|
- 每30秒仅1次检查
|
|||
|
|
- 检查本身几乎无开销(< 1ms)
|
|||
|
|
- 不影响任务执行速度
|
|||
|
|
|
|||
|
|
4. **调试友好**
|
|||
|
|
- 控制台输出详细日志
|
|||
|
|
- 可以看到内存使用趋势
|
|||
|
|
- 方便排查内存问题
|
|||
|
|
|
|||
|
|
### ⚠️ 缺点
|
|||
|
|
|
|||
|
|
1. **浏览器兼容性**
|
|||
|
|
- Firefox/Safari不支持 `performance.memory`
|
|||
|
|
- 但会优雅降级,不影响功能
|
|||
|
|
|
|||
|
|
2. **紧急清理影响**
|
|||
|
|
- 删除详细result数据后,任务详情弹窗会显示"暂无数据"
|
|||
|
|
- 但这只在内存极度紧张时发生(>85%)
|
|||
|
|
- 实际场景中很少触发
|
|||
|
|
|
|||
|
|
3. **误判可能**
|
|||
|
|
- 如果其他标签页占用大量内存,也可能触发清理
|
|||
|
|
- 但清理动作本身是无害的
|
|||
|
|
|
|||
|
|
## 六、实际场景测试
|
|||
|
|
|
|||
|
|
### 场景1:正常运行(100个Token)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[内存监控] 已启动
|
|||
|
|
[内存监控] 内存使用率: 45.2% (924MB / 2048MB) - 正常
|
|||
|
|
[内存监控] 内存使用率: 52.8% (1081MB / 2048MB) - 正常
|
|||
|
|
[内存监控] 内存使用率: 58.3% (1194MB / 2048MB) - 正常
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**结论**:不触发任何清理,正常运行 ✅
|
|||
|
|
|
|||
|
|
### 场景2:高内存压力(500个Token)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[内存监控] 已启动
|
|||
|
|
[内存监控] 内存使用率: 65.4% (1339MB / 2048MB) - 正常
|
|||
|
|
⚠️ [内存监控] 内存使用率: 72.1% (1477MB / 2048MB) - 触发标准清理
|
|||
|
|
✅ [强制清理] 清理了 150 个已完成任务的进度数据
|
|||
|
|
[内存监控] 内存使用率: 68.9% (1411MB / 2048MB) - 正常
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**结论**:触发标准清理,成功释放内存 ✅
|
|||
|
|
|
|||
|
|
### 场景3:极限压力(1000个Token + 其他页面占用)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[内存监控] 已启动
|
|||
|
|
[内存监控] 内存使用率: 78.5% (1608MB / 2048MB) - 正常
|
|||
|
|
⚠️ [内存监控] 内存使用率: 81.2% (1663MB / 2048MB) - 触发标准清理
|
|||
|
|
⚠️ [内存监控] 内存使用率: 87.4% (1790MB / 2048MB) - 触发紧急清理
|
|||
|
|
🗑️ [紧急清理] 已删除 800 个Token的详细结果数据
|
|||
|
|
♻️ [紧急清理] 已触发强制垃圾回收
|
|||
|
|
[内存监控] 内存使用率: 65.8% (1348MB / 2048MB) - 正常
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**结论**:紧急清理成功避免崩溃,释放约440MB内存 ✅
|
|||
|
|
|
|||
|
|
## 七、是否需要实施?
|
|||
|
|
|
|||
|
|
### 📊 推荐指数:⭐⭐⭐ (3/5)
|
|||
|
|
|
|||
|
|
### 适合场景
|
|||
|
|
|
|||
|
|
**✅ 强烈推荐**:
|
|||
|
|
- 经常执行200+个Token的批量任务
|
|||
|
|
- 用户反馈过浏览器卡顿或崩溃
|
|||
|
|
- 追求极致稳定性
|
|||
|
|
|
|||
|
|
**⚠️ 可选**:
|
|||
|
|
- 主要执行10-100个Token(内存压力小)
|
|||
|
|
- 对代码简洁性有要求
|
|||
|
|
- 不想增加监控开销
|
|||
|
|
|
|||
|
|
**❌ 不推荐**:
|
|||
|
|
- 只执行少量Token(< 10个)
|
|||
|
|
- 内存充足(16GB+)且只开一个标签页
|
|||
|
|
|
|||
|
|
### 决策建议
|
|||
|
|
|
|||
|
|
**我的建议**:
|
|||
|
|
|
|||
|
|
1. **如果用户规模未知** → **建议实施** ✅
|
|||
|
|
- 作为一个"保险机制"
|
|||
|
|
- 几乎无成本,但能避免极端情况
|
|||
|
|
|
|||
|
|
2. **如果明确用户场景** → **根据规模决定**
|
|||
|
|
- 10-100个Token:可不实施
|
|||
|
|
- 200+个Token:建议实施
|
|||
|
|
- 500+个Token:强烈建议实施
|
|||
|
|
|
|||
|
|
3. **开发阶段** → **建议实施** ✅
|
|||
|
|
- 帮助发现内存泄漏
|
|||
|
|
- 查看内存使用趋势
|
|||
|
|
- 优化清理策略
|
|||
|
|
|
|||
|
|
## 八、轻量级替代方案
|
|||
|
|
|
|||
|
|
如果觉得完整的P4过于复杂,可以考虑简化版:
|
|||
|
|
|
|||
|
|
### 简化版:只在任务结束时检查
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
const completeBatchExecution = () => {
|
|||
|
|
// 任务结束时检查一次内存
|
|||
|
|
const memory = getMemoryUsage()
|
|||
|
|
if (memory && memory.used / memory.limit > 0.7) {
|
|||
|
|
console.warn(`⚠️ 内存使用率较高: ${((memory.used / memory.limit) * 100).toFixed(1)}%`)
|
|||
|
|
forceCleanupTaskProgress()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ... 其他清理代码
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- 代码极简(5行)
|
|||
|
|
- 无定时器开销
|
|||
|
|
- 仍能提供基本保护
|
|||
|
|
|
|||
|
|
**缺点**:
|
|||
|
|
- 只在任务结束时清理
|
|||
|
|
- 无法在执行过程中防护
|
|||
|
|
|
|||
|
|
## 九、总结
|
|||
|
|
|
|||
|
|
**P4内存监控机制**是一个可选的"安全气囊"功能:
|
|||
|
|
|
|||
|
|
| 特性 | 评价 |
|
|||
|
|
|------|------|
|
|||
|
|
| 必要性 | ⭐⭐⭐ 中等 |
|
|||
|
|
| 实施难度 | ⭐⭐ 简单 |
|
|||
|
|
| 性能开销 | ⭐ 极低 |
|
|||
|
|
| 代码复杂度 | ⭐⭐⭐ 中等 |
|
|||
|
|
| 用户体验影响 | ⭐ 极低(正常情况无影响) |
|
|||
|
|
| 稳定性提升 | ⭐⭐⭐⭐ 显著(极端场景) |
|
|||
|
|
|
|||
|
|
**最终建议**:
|
|||
|
|
- 如果追求**极致稳定性** → 实施完整版
|
|||
|
|
- 如果追求**代码简洁** → 实施简化版或不实施
|
|||
|
|
- 如果追求**平衡** → 实施简化版
|
|||
|
|
|
|||
|
|
**您可以根据实际用户反馈决定**:
|
|||
|
|
- 如果从未出现内存问题 → 暂不实施
|
|||
|
|
- 如果偶尔有用户反馈卡顿 → 实施简化版
|
|||
|
|
- 如果频繁出现崩溃 → 实施完整版
|
|||
|
|
|