前言
在上一章中 UE4 C++ FMemory内存管理从入门到深入 介绍了FMemory的内存管理方式, 这节介绍了UObject的内存管理,UObject的内存管理是建立在FMemory这套的底层内存分配机制上的。本文重点介绍UObject的UPROPERTY标记内存回收,至于UObject的内存分配等等其他大致略过。
UObject的内存分配
UObject的内存分配大致流程
具体流程如上所示, UObject的内存分配器叫GUObjectAllocator,其内存分配是在FMemory的基础上进行一个封装而已, 这里不介绍太多。至于UObject被分配内存后,每一个UObject对象被一个FUObjectItem对象包裹放到GUObjectArray数组中,进行全局的管理。FUObjectItem和GUObjectArray下面在详细说。
UObject 内存垃圾回收(GC)总体执行流程
这里主要讲解UPROPERTY标记法的UObject变量内存回收机制。
GC函数调用栈
大体总结为:
分析UPROPERTY, 生成标记引用信息
UClass注册时分析所有UPROPERTY标注的变量, 生成类的FGCReferenceTokenStream信息
FGCReferenceTokenStream类内部变量是个TArray<uint32>,包含了UObject里UPROPERTY变量的引用信息(FGCReferenceInfo), 对于GC Mark阶段,用于分析被UPROPERTY标记的UObject在引用链是否有效
GC条件触发
UE4触发GC的执行入口函数的 UEngine::PerformGarbageCollectionAndCleanupActors()
而是否触发GC的判断是每帧都在进行的,一旦满足条件就进行GC
UE4每隔一段时间GC一次,这个“一段时间”可以在引擎里进行设置
GC执行
从上面的图中已经看到GC主要分为两个阶段:
GC Mask(GC标记Object状态阶段)和UObject Memory Collect内存清除
GC对象和GC状态
先看看UObject GC 状态有关的主要数据结构
可以看到每一个UObject对象都被一个FUObjectItem管理着, FChunkedFixedUObjectArray管理着一个个内存块,一个内存块包含N个FUObjectItem对象,你可以简单的这么认为,GUObjectArray是管理FUObjectItem(UObject)对象的一个大型数组。 FUObjectItem对象有个int32 Flags变量专门用于指明UObject对象此时的GC状态
举个例子,我们调用 AActor::Destroy 销毁一个Actor对象
AActor执行Destroy的时候用 将自己所在的FUObjectItem标记状态为EInternalObjectFlags::PendingKill
这个时候UObject对象是处于随时可能被内存回收的,因此判断一个UObject对象是否是安全的,不仅仅判断是否为空,也要判断其是否处于 PendingKill状态
if(Object != nullptr && !Object->IsPendingKill()
至于前面的文章中说过调用UObject::AddToRoot()就不会被UE4进行内存回收, 是因为此时FUObjectItem标记状态为EInternalObjectFlags::RootSet,这种状态下UObject是不会被内存回收的。具体的原因下面再说。
GC标记过程
GC标记过程主要发生在GarbageCollection.cpp::CollectGarbageInternal, 此时会对所有的UObject对象先进行可达性分析,可达性分析主要是分析一个UObject对象是否可达的,如果不可达就标记为EInternalObjectFlags::Unreachable,才被认为是无效内存进一步参与下面的GC步骤被回收内存。主要分为两部分(蓝色部分)
1.先把没有KeepFlags 和EInternalObjectFlags::GarbageCollectionKeepFlags标志的所有UObject对象标记为EInternalObjectFlags::Unreachable,这个过程是多线程的。
在MarkObjectsAsUnreachable并行判断可达性的时候看到这样一句代码,对于处于EInternalObjectFlags::RootSet的对象并不会被标记为不可达状态,这解释了上面UObject::AddToRoot可以让UObject对象不会被GC的说法。
2. 对UPROPERTY标记的Object类对象进行可达性分析
这个分析过程ProcessObjectArray是多线程进行的
在分析可达性中,用到了UClass的变量引用信息FGCReferenceTokenStream。 针对UPROPERTY标记UObject变量的各种情况(单个UObject对象,TArray<UObject>, 包含UObject的TMap等等)进行不同的归类进行对象的可达性分析,如果引用链上UObject被判定不可达,也会被标记为EInternalObjectFlags::Unreachable
上面两部分可达性分析,将认定无效的UObject对象进行了EInternalObjectFlags::Unreachable标记
收集不可达的UObject对象
在GatherUnreachableObjects收集不可达的FUObjectItem对象,放在一个全局不可达对象数组
开始清理Object阶段
UnhashUnreachableObjects,对不可达的UObject进行进一步清理,清除UObject部分信息,如linker's export table等等,最后标记为RF_BeginDestroyed(注意这里是Object的状态而非FUObjectItem状态的标记)
完成UObject信息清理阶段
UObject已经在上一步进行初步信息清理, 这里得进行所有信息的清理,最后标记为RF_FinishDestroyed
内存回收阶段
上面阶段的UObject最终被清除完各种信息, 被标记为RF_FinishDestroyed,之后并对UObject进行真正的内存回收。UObject的内存回收在FAsyncPurge中进行
可以看到FAsyncPurge继承FRunnable,是UE4多线程情景下使用的一个类, 但是在内存回收时
UE4可能存在单线程(同步)或者多线程(异步)内存回收两种情况:
资料参考
【1】浅析UE4垃圾回收