内存区域 | 管理方式 | 常见泄漏类型 |
---|---|---|
托管堆(Managed) | GC自动回收 | 静态引用/事件订阅未取消 |
原生堆(Native) | 手动管理 | 非托管资源未释放 |
桥接层 | GCHandle/PInvoke | 跨语言引用未正确释放 |
// 案例1:静态变量持有对象 public class GameManager { public static ListAllEnemies = new List (); // 敌人销毁时未从列表移除将导致泄漏 } // 案例2:未取消的事件订阅 void OnEnable() { EventManager.OnBattleEnd += HandleBattleEnd; } void OnDisable() { EventManager.OnBattleEnd -= HandleBattleEnd; // 若未执行将泄漏 } // 案例3:非托管资源未释放 public class NativePluginWrapper : IDisposable { private IntPtr _nativePtr; ~NativePluginWrapper() { if(_nativePtr != IntPtr.Zero) { // 需调用NativeFree(_nativePtr); } } }
// 运行时主动捕获快照 using UnityEngine.Profiling.Memory.Experimental; public class MemorySnapshotTrigger : MonoBehaviour { [SerializeField] KeyCode _snapshotKey = KeyCode.F12; void Update() { if(Input.GetKeyDown(_snapshotKey)) { CaptureSnapshot(); } } static void CaptureSnapshot() { MemoryProfiler.TakeSnapshot( "snapshot_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".snap", (success, path) => Debug.Log($"Snapshot saved: {path} (success:{success})") ); } }
# 构建时生成完整符号文件 BuildPlayerOptions buildOptions = new BuildPlayerOptions(); buildOptions.options |= BuildOptions.Development; buildOptions.options |= BuildOptions.AllowDebugging; buildOptions.options |= BuildOptions.ForceEnableAssertions;
sequenceDiagram participant User participant Profiler participant Game User->>Game: 进入疑似泄漏场景 User->>Profiler: 捕获快照A Game->>Game: 执行泄漏操作N次 User->>Profiler: 捕获快照B Profiler->>Profiler: 对比A/B快照 Profiler-->>User: 展示增长对象Top10
// 自动记录内存变化的调试组件 public class MemoryWatcher : MonoBehaviour { struct MemoryRecord { public long TotalMemory; public int GcCollectionCount; public DateTime Time; } private List_records = new List (); private bool _isTracking; void Update() { if(Input.GetKeyDown(KeyCode.F10)) StartTracking(); if(Input.GetKeyDown(KeyCode.F11)) StopAndAnalyze(); } void StartTracking() { _records.Clear(); _isTracking = true; StartCoroutine(TrackMemory()); } IEnumerator TrackMemory() { while(_isTracking) { GC.Collect(); // 强制GC确保数据准确性 yield return new WaitForSeconds(1); _records.Add(new MemoryRecord { TotalMemory = Profiler.GetTotalAllocatedMemoryLong(), GcCollectionCount = GC.CollectionCount(0), Time = DateTime.Now }); } } void StopAndAnalyze() { _isTracking = false; StringBuilder report = new StringBuilder("Memory Change Report:\n"); for(int i=1; i<_records.Count; i++) { long delta = _records[i].TotalMemory - _records[i-1].TotalMemory; report.AppendLine($"{_records[i].Time:T} | Delta: {delta / 1024}KB"); } Debug.Log(report); } }
// 使用WeakReference检测对象泄漏 public class LeakDetectorwhere T : class { private WeakReference _weakRef; private string _creationStack; public LeakDetector(T target) { _weakRef = new WeakReference(target); _creationStack = Environment.StackTrace; } public bool IsAlive => _weakRef.IsAlive; public void CheckLeak() { GC.Collect(); GC.WaitForPendingFinalizers(); if(_weakRef.IsAlive) { Debug.LogError($"Potential leak detected!\nCreation Stack:\n{_creationStack}"); #if UNITY_EDITOR Debug.Break(); #endif } } } // 使用示例 void SpawnEnemy() { var enemy = new Enemy(); new LeakDetector (enemy).CheckLeak(); }
// 使用Profiler标记Native内存区域 public class NativeMemoryTracker : IDisposable { private IntPtr _ptr; private int _size; private string _tag; public NativeMemoryTracker(int size, string tag) { _size = size; _tag = tag; _ptr = Marshal.AllocHGlobal(size); Profiler.EmitNativeAllocSample(_ptr, (ulong)size, 1); } public void Dispose() { Profiler.EmitNativeFreeSample(_ptr, 1); Marshal.FreeHGlobal(_ptr); _ptr = IntPtr.Zero; } }
// LeakDetectionRules.json { "rules": [ { "type": "System.WeakReference", "maxCount": 50, "severity": "warning" }, { "type": "UnityEngine.Texture", "maxSizeMB": 100, "severity": "critical" } ] }
public class AutoLeakScanner { public void RunScan() { var allObjects = Resources.FindObjectsOfTypeAll(); var typeCounts = new Dictionary (); var typeSizes = new Dictionary (); foreach(var obj in allObjects) { string typeName = obj.GetType().FullName; typeCounts[typeName] = typeCounts.GetValueOrDefault(typeName, 0) + 1; typeSizes[typeName] = typeSizes.GetValueOrDefault(typeName, 0) + Profiler.GetRuntimeMemorySizeLong(obj); } AnalyzeResults(typeCounts, typeSizes); } private void AnalyzeResults(Dictionary counts, Dictionary sizes) { // 加载规则文件并验证 var rules = LoadDetectionRules(); foreach(var rule in rules) { if(counts.TryGetValue(rule.type, out int count)) { if(count > rule.maxCount) { ReportLeak(rule, count, sizes[rule.type]); } } } } }
// android/app/build.gradle android { buildTypes { debug { debuggable true jniDebuggable true packagingOptions { doNotStrip '**/*.so' } } } }
DTPlatformVersion latest UIRequiredDeviceCapabilities arm64 EnableDebugging
运行 HTML
优化方向 | 实现方案 | 效果提升 |
---|---|---|
过滤系统对象 | 忽略UnityEngine/System命名空间 | 60% |
增量快照 | 仅记录两次快照之间的差异 | 70% |
压缩存储 | 使用LZ4压缩快照文件 | 50% |
// 使用JobSystem并行分析 [BurstCompile] struct MemoryAnalysisJob : IJobParallelFor { [ReadOnly] public NativeArrayObjects; [WriteOnly] public NativeHashMap .ParallelWriter TypeCounts; public void Execute(int index) { var typeName = Objects[index].TypeName; TypeCounts.AddOrUpdate(typeName, 1, (key, val) => val + 1); } }
现象:每次打开关闭UI界面,内存增长2-3MB且不释放
分析:
使用Memory Profiler发现多个重复Texture2D实例
定位到未正确调用Resources.UnloadAsset(unusedAtlas)
修复:
public class UIManager : MonoBehaviour { private Dictionary_loadedAtlases = new Dictionary (); void UnloadUnusedAtlases() { var keysToRemove = new List (); foreach(var pair in _loadedAtlases) { if(pair.Value.referenceCount == 0) { Resources.UnloadAsset(pair.Value); keysToRemove.Add(pair.Key); } } foreach(var key in keysToRemove) { _loadedAtlases.Remove(key); } } }
现象:场景切换后仍有未释放的协程运行
分析:
使用WeakReference检测到Coroutine对象存活
定位到未正确调用StopCoroutine
修复:
public class SafeCoroutineRunner : MonoBehaviour { private Dictionary_runningCoroutines = new Dictionary (); public void StartTrackedCoroutine(IEnumerator routine) { var coroutine = StartCoroutine(WrapCoroutine(routine)); _runningCoroutines[routine] = coroutine; } private IEnumerator WrapCoroutine(IEnumerator routine) { yield return routine; _runningCoroutines.Remove(routine); } public void StopTrackedCoroutine(IEnumerator routine) { if(_runningCoroutines.TryGetValue(routine, out var coroutine)) { StopCoroutine(coroutine); _runningCoroutines.Remove(routine); } } }
通过本方案,开发者可系统化解决IL2CPP环境下的内存泄漏问题,实现:
精准定位:结合托管与非托管内存分析
高效修复:提供典型场景修复模式
预防机制:建立自动化检测体系
建议将内存分析纳入每日构建流程,结合自动化测试框架实现内存使用基线管理,确保项目内存健康度持续达标。