网易首页 > 网易号 > 正文 申请入驻

UE5版本中GC的改动

0
分享至


【USparkle专栏】如果你深怀绝技,爱“搞点研究”,乐于分享也博采众长,我们期待你的加入,让智慧的火花碰撞交织,让知识的传递生生不息!

这是侑虎科技第1958篇文章,感谢作者南京周润发供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:

https://www.zhihu.com/people/xu-chen-71-65

总览

UE5的GC,相对UE4做了一些改动,但本质没变,依然是基于UObject的标记清扫算法,许多改动都属于优化。

先看GC的各个阶段:

  • 遍历所有Object,标记为不可达

改动较少,增加了RefCount引用方式;更改了Unreachable Flag;增加了单独的RootArray作为根集。

  • 遍历引用关系做可达性分析

改动较多,Batch模式的引用收集;UObject对ClassPrivate和Outer的引用从Token中移除;增加了引用收集的Debug模式。

  • 清理不可达Object

差别不大,依然是分帧Purge,对每个UObject依次执行BeginDestroy, Destroy, FinishDestroy。

除此之外,还有实验性的Increment可达性分析模式,想完全避免可达性分析造成的一帧卡顿,算是一个最大的不同。

对于GC Cluster,变化不大。

另外还有一些其他Tips,比如IsPendingKill接口改成了IsGarbage;ObjectFlags增加了Reachable0,Reachable1,Reachable2等等。

文章中所使用的为UE5.7。

前篇:

一、加入引用计数机制

1. RefCounted Flag & RefCount

观察InternalObjectFlags,发现多了一个叫RefCounted的Flag,说明UE的GC在标记清扫的基础上,又加入了引用计数机制,算是一个不小的改动。

引用计数是另一种GC方式,Python中就使用了,简单概括为每个Object记录自己被多少个其他Object引用了,当发现引用计数为0时,就判定自己为垃圾。至于垃圾的清理,可以选择为0时立即清理,也可以选择之后一起清理,反正垃圾是不会再被重新引用到的。

enum class EInternalObjectFlags : int32
{
None = 0,
//...
RefCounted UE_DEPRECATED(5.7, "Use GetRefCount() to determine if a refcount exists instead.") = 1 << 29, ///< Object currently has ref-counts associated with it.
//...
};

但有了Flag还不够,得给每个Object再定义一个RefCount属性,记录被引用的次数。UE5.7之前,在FUObjectItem中有一个专门的int32 RefCount属性,但UE5.7开始把RefCount和Flags一起打包成了int64。Flag存在高32位里,RefCount存在低32位里。



这样有个好处,RefCount通常用不了int32这么大,因此可以把更多位数给EInternalObjectFlags用,比如添加一些我们自定义的Flag,或者UE引擎自己扩展。


2. 操作接口

添加RefCount:

void UObjectBase::AddRef() const
{
FUObjectItem* ObjectItem = GUObjectArray.ObjectToObjectItem(this);
ObjectItem->AddRef();
}

减去RefCount:

void UObjectBase::ReleaseRef() const
{
FUObjectItem* ObjectItem = GUObjectArray.ObjectToObjectItem(this);
ObjectItem->ReleaseRef();
}

获取RefCount:

在引擎内部代码,比如GC过程中,才有获取RefCount的需求,因此UObject上没有获取RefCount的接口,只有FUObjectItem上有。

FORCEINLINE int32 GetRefCount() const
{
return RefCount;
}

哪些地方会给UObject加上引用计数?

引用计数管理UObject显然更容易出错,万一AddRef/ReleaseRef不成对,就会造成UObject泄漏,引擎中目前也只有StrongObjectPtr使用了。而且是用了RAII模式,在构造和析构中操作RefCount,防止泄漏。StrongObjectPtr可以对一个UObject添加强引用,防止被GC,和AddToRoot类似。下面是StrongObjectPtr中操作的代码:

FORCEINLINE_DEBUGGABLE void Reset(ObjectType* InNewObject)
{
if (InNewObject)
{
if (Object == InNewObject)
{
return;
}


if (Object)
{
// UObject type is forward declared, ReleaseRef() is not known.
// So move the implementation to the cpp file instead.
UEStrongObjectPtr_Private::ReleaseUObject(Object);
}
InNewObject->AddRef();
Object = InNewObject;
}
else
{
Reset();
}
}

3. 为什么要使用引用计数管理UObject

一个原因可能是能减少可达性分析时间。

可达性分析阶段需要遍历Object间的所有引用关系,耗时正比于引用数量。

比如下图,4个UObject相互引用,总共要遍历10个引用关系。但假如都用引用计数管理,可以省略遍历开销,直接判定四个对象都可达。


但是理论上讲,引用计数方式只是把遍历开销分摊到了运行时维护引用计数的加减上。而且注意到上面例子其实是环形引用的情况,没有外部引用情况下,四个UObject都该被判定为垃圾才对。因此一个完善的垃圾回收实现,不会只用引用计数,比如Python的FullGC就用的标记-清扫算法,作为辅助。UE依然把标记-清扫作为主GC流程,引用计数只是小小的辅助,但使用是绝对要注意。

另一个原因是单独针对StrongObjectPtr的优化。

StrongObjectPtr之前的实现是把包起来的UObject单独放到一个大数组里,然后这个数组被一个FGCObject添加引用。这样有个问题,当数组元素很多时,添加需要遍历全数组,判断是否已添加过,而删除时同样需要遍历全数组,找到元素并删除,遍历的时间很可观。


4. RefCount如何影响可达性分析

其实RefCount等同于Root,观察AddRef调用链,最终会执行AddToRoot操作。



在第一步标记所有UObject不可达后,会遍历GRoots容器,把其中的UObject变成可达,那么RefCounted的Object也变成可达了。

二、可达性分析阶段的优化

1. FPrefetchingObjectIterator

首先介绍一个挺细节的ObjectIterator,基于经验做了Prefetch,尽量避免访问Object的Outer和ClassPrivate造成的CacheMiss。整个可达性分析阶段都充分应用了Prefetch技术。

首先观察下面代码,会遍历一组UObject,然后分别访问它们的Class和Outer:

FORCEINLINE_DEBUGGABLE void ProcessObjects(DispatcherType& Dispatcher, TConstArrayView 

 CurrentObjects 
)
{
for (FPrefetchingObjectIterator It(CurrentObjects); It.HasMore(); It.Advance())
{
UObject* CurrentObject = It.GetCurrentObject();
UClass* Class = CurrentObject->GetClass();
UObject* Outer = CurrentObject->GetOuter();
//...
}
}

这样必然会导致读Object内存操作,相比读CPU Cache肯定是更慢的。粗略估计,访问内存的延迟是60ns,而访问CPU L1 Cache的延迟是1ns,当中相差不少。


既然已经知道要访问这么多Object的ClassPrivate和Outer属性,那么能不能提前把它们读取到CPU Cache中,加快访问速度?这就是FPrefetchingObjectIterator做的事情,使用了操作系统的Prefetch接口,异步的提出一个预读请求,不需要当场等待内容返回到CPU Cache,CPU可以继续执行后面指令。

FPrefetchingObjectIterator迭代器在++时,会Prefetch后面第六个的Outer和ClassPrivate的Schema,以及后面第16个的Class。这些数字都是经验值,既需要保证访问到这块内存时,其已经被加载到了CPU Cache,又不能Prefetch太多,从而挤占CPU Cache。


示意图如下:


2. 基于Batch的Object引用收集

Batch

UE4怎么做的:

给定一个待分析可达性的Objects数组,会不断遍历其引用,做广度优先搜索,最终遍历完所有可达的UObject,完成任务。其中碰到StructArray结构时,还会形成类似递归的情况,处理代码更复杂些。整个过程由一个大的ProcessObjectArray函数实现,总共有600+行。

UE5的Batch:

UE5使用了Batch的遍历方式,首先把ProcessObjectArray逻辑拆成多个函数,函数逻辑精简,最长不超过90行,对CPU指令Cache更友好,然后多个函数形成执行流;大概500个Object作为一个Batch,依次经过这些执行流,更少的数据量对应更少的内存使用量,让CPU Cache足够应对;另外每段处理函数逻辑变简单后,可以更容易地使用上面提到的Prefetch技术做Cache预取,进一步提升效率。


Schema

首先,UE5加入了Schema的概念,其实就是UE4里的TokenStream,记录Object带引用的各成员地址偏移,以及成员类型。


ProcessObjectArray

下面是ProcessObjectsArray的主要流程,首先执行ProcessObjects,收集引用到Dispatcher,Dispatcher的具体类型为BatchedDispatcher。CurrentObjects处理完后,再尝试从全部的ObjectsToSerialize数组中取出BlockSize数量的UObject,继续执行ProcessObjects做引用收集。如果ObjectsToSerialize也空了,就执行FlushWork,把引用到的UObject都添加到ObjectsToSerialize中继续。


这样就能把标记阶段分成多个Pass,每个Pass处理一点Object,流程示意图如下:


ProcessObjects

ProcessObjects用于收集CurrentObjects的所有引用,FPrefetchingObjectIterator已在上面提过。注意到这里把Class和Outer的处理单独提出来了,UE4还是在正常的Schema里处理它们。可能是考虑到Class和Outer是每个UObject必备的引用,提出来统一处理能减小Schema大小。


VisitMembers函数是真正的处理Schema里定义的各种引用,包括UPROPERTY引用单个Object,TArray 数组引用等等,这里只给出单个Object引用和StructArray引用两个常见例子。


VisitStructArray只会把StructArray缓存到对应容器中,后面Flush时再处理:

FORCEINLINE_DEBUGGABLE void QueueStructArray(FSchemaView Schema, uint8* Data, int32 Num)
{
StructBatcher.PushStructArray(Schema, Data, Num);
}

ImmutableReference & KillableReference

注意到对于单个UObject的引用,分了ImmutableReference和KillableReference(FMutableReference),其实只有UObject对Class和Outer的引用属于Immutable,其他都属于Killable。这对UE4而言并没有提出新的内容,只是老内容包装成了新概念。

比如有如下代码,想强制删除B,但B又是A的属性,被引用了

A->Prop = B;

B->MarkAsGarbage();

对于Class和Outer引用,非常强力,强到能阻止对B的强删,让B继续以Garbage的形式存在。

对于普通的KillableReference,无法阻止,同时A->Prop属性会变成nullptr。

因此查看它们的初始化方式,FMutableReference是存储了指向属性的指针。

// Retains address of reference to allow killing it
struct FMutableReference { UObject** Object; };
struct FImmutableReference { UObject* Object; };

FlushWork

依次处理StructArray和其他Object引用,主要工作就是遍历各种Array。

FORCEINLINE_DEBUGGABLE void FlushWork(DispatcherType& Dispatcher)
{
if constexpr (DispatcherType::bBatching)
{
if (Dispatcher.FlushToStructBlocks())
{
ProcessStructs(Dispatcher);
}


Dispatcher.FlushQueuedReferences();
}
}

最终通过HandleValidReference函数,把UObject标记为可达,并插入到ObjectsToSerialize数组中,以待后续遍历。


三、配套Debug功能的完善

1. gc.history协助找泄露

其中一个比较实用的功能是可以记录下上次GC过程中,每个对象被谁引用了,方便排查UObject泄露问题。

看个例子,假如有如下代码:

FActorSpawnParameters SpawnParameters;
SpawnParameters.Name = TEXT("TestObject123");
APawn* TestPawn = GetWorld()->SpawnActor (SpawnParameters);
USceneComponent* TempObject = NewObject (TestPawn, USceneComponent::StaticClass());
TempObject->RegisterComponent();
{
TStrongObjectPtr StrongPtr(TempObject);
TestPawn->Destroy();
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true);
TempObject->MarkAsGarbage();
}


GEngine->Exec(GetWorld(), TEXT("OBJ Refs Name=TestObject123"));

在有StrongObjectPtr的情况下,即使MarkAsGarbage,这次GC也是删不掉TempObject和TestPawn的,在严格的逻辑下,算是UObject泄露。Obj Refs的输出也印证了:

LogReferenceChain: (Garbage) Pawn /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123 is not currently reachable. Try using GC history to debug transient leaks with 'gc.historysize 1'

输出中只能看到UObject泄露了,但打印Obj Refs时,引用现场已经没了,不知道为什么泄露。过去,要调试这个泄露,得先记下Object地址,再去GC下数据断点,再对Flags下断点等等操作,非常麻烦。

现在UE5加了HistorySize的Debug功能,会对上次GC存储Snapshot,之后再输出。

首先通过如下控制台指令开启:

gc.ForceEnableGCProcessor
gc.Historysize 1

然后把Exec代码改成:

FReferenceChainSearch::FindAndPrintStaleReferencesToObject(TestPawn, EPrintStaleReferencesOptions::Log);

接着就能从GC History中获取到历史GC引用了,快速定位到泄露原因,非常方便:

LogLoad: Old Pawn /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123 not cleaned up by GC! Garbage object SceneComponent /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123.SceneComponent_0 was previously being referenced by NULL:
(refcounted<1>) (Garbage) SceneComponent /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123.SceneComponent_0
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^ This reference is preventing the old Pawn from being GC'd ^
-> UObject* UObject::Outer = (Garbage) Pawn /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123

2. 实现原理

UE5中,新增了用于Debug的TDebugReachabilityProcessor,在可达性分析阶段,当第一次遍历到一个Object时,把当前路径记录下来即可。


四、增量式可达性分析

1. 为什么要做增量式可达性分析

增量式的垃圾回收,目前还是Experimental阶段,个人感觉有点意义不明。

Documentation | Epic Developer Community

https://dev.epicgames.com/documentation/en-us/unreal-engine/incremental-garbage-collection-in-unreal-engine

一个典型例子是Lua虚拟机实现,采用了黑白灰三色标记,UE的实现也比较类似。

开启方式:

DefaultEngine.ini加入下面配置:

[ConsoleVariables]
gc.AllowIncrementalReachability=1 ; enables Incremental Reachability Analysis
gc.AllowIncrementalGather=1 ; enables Incremental Gather Unreachable Objects
gc.IncrementalReachabilityTimeLimit=0.002 ; sets the soft time limit to 2ms

为什么有必要把可达性分析阶段改成增量式的?

GC耗时可以分成三个部分:所有UObject先标记不可达,可达性分析,销毁垃圾。其中,销毁垃圾本身是分帧的,不会造成卡顿。前两个部分过去不能分帧,而可达性分析阶段是耗时主要部分,通常超过第一部分十倍,如果游戏希望维持60帧或更高帧率运行,这绝对会是一个阻碍。为了缓解这个卡顿,UE很早就提出了Cluster优化方案,现在又进一步提出了增量GC方案。

为什么之前可达性分析不能分帧执行?

考虑这样的场景,假如可达性分析要多帧完成,在中间执行了A.XX = B语句,使A引用到B,但A已经完成了可达性分析,UE识别不到这个新加的引用。那么B就有可能被错误当成垃圾回收掉。


可是这是官方文档的解释,但个人感觉不太符合正常使用场景,还没想通。不妨把B分成下面两种情况:

1. B在这次GC开始前已创建,如果此时B没有其他引用了,那么代码里又是如何获取到B的?存裸指针当然可以,但本身也是不规范的做法。

2. B在这次GC开始后才创建,但新创建UObject默认是Reachable的,不会被这次GC删除。这点和Lua是有区别的,因为Lua里Object创建后默认不可达。


这个也许和增量回收还处于Experimental有关,逻辑还不成熟,不妨顺着UE思路继续往下看实现。

2. Write Barrier

解决这种问题还是要靠Write Barrier。得益于UE5中把裸指针替换为TObjectPtr,使得A.XX = B这种语句能被捕获到了。

TObjectPtr会在Operator =函数里做检查,如果发现正在GC,就会立即把B作为可达对象,加到全局GReachableObjects或GReachableClusters链表中。




之后以GReachableObjects为例,在可达性分析的PerformReachabilityAnalysisPass函数开头,会首先把GReachableObjects容器中的Object作为InitialObjects,当成初始可达对象,然后继续做可达性分析遍历。


这只是TObjectPtr的处理,我们也会定义些自定义逻辑,然后使用AddReferenceObjects函数添加引用,这就不太好处理了。

3. 如何实现分帧

回顾之前的ProcessObjectsArray函数,已经实现了Batch形式的Object遍历,每趟处理500个Object,那么分帧也是比较方便实现的。这一段TimeLimit逻辑控制了每帧可以花费多少时间做可达性分析,超过了就给下一帧继续执行,会从CollectGarbage入口进来。


既然分帧,就要保留当前GC进度,要知道还有哪些Object需要遍历。这些信息就存储在Context.InitialObjects数组里。因为可达性分析阶段是多线程执行的,因此每个线程都有自己的Context。

其实不仅可达性分析阶段做了增量式处理,后面的收集不可达对象阶段也支持了增量执行。只是耗时本身不高,逻辑也更简单,进度保留在Context中即可。

开启增量GC后的Trace如下,可以看到实现了分帧,但还不能做到完美的Timelimit分帧:


五、其他Tips

1. ReachabilityFlag0, ReachabilityFlag1

UE5在标记所有Object不可达时,不再遍历所有Object并设置UnReachable标记,而是交替使用两种ReachabilityFlag,同时依然有UnReachableFlag,用来表示真正的不可达Object:

ReachabilityFlag0 = 1 << 14, ///< One of the flags used by Garbage Collector to determine UObject's reachability state
ReachabilityFlag1 = 1 << 15, ///< One of the flags used by Garbage Collector to determine UObject's reachability state
ReachabilityFlag2 = 1 << 16, ///< One of the flags used by Garbage Collector to determine UObject's reachability state


Unreachable = 1 << 28, ///< Object is not reachable on the object graph.

交替两个ReachabilityFlag代码如下:

FORCEINLINE static void SwapReachableAndMaybeUnreachable()
{
// It's important to lock the global UObjectArray so that the flag swap doesn't occur while a new object is being created
// as we set the GReachableObjectFlag on all newly created objects
GUObjectArray.LockInternalArray();

Swap(ReachableObjectFlag, MaybeUnreachableObjectFlag);

// Maintain the old flag variables for backwards compatibility
PRAGMA_DISABLE_DEPRECATION_WARNINGS
UE::GC::GReachableObjectFlag = ReachableObjectFlag;
UE::GC::GMaybeUnreachableObjectFlag = MaybeUnreachableObjectFlag;
PRAGMA_ENABLE_DEPRECATION_WARNINGS

GUObjectArray.UnlockInternalArray();
}

如此能实现这一帧Flag0表示可达,Flag1表示可能不可达,下一帧反过来,下下一帧再反过来。

然后一次GC结束后,PostCollectGarbageImpl,GatherUnreachableObjects会遍历所有UObject,把依然有MaybeUnreachableObjectFlag的Object视为垃圾,标记上Unreachable Flag。



遍历完成后,插入到专门储存待清理对象的GUnreachableObjects数组中。


2. MarkAsGarbage取代MarkAsPendingKill

UE4对于强删的Object,比如Actor,会MarkAsPendingKill,UE5把接口改成了MarkAsGarbage:

inline void MarkAsGarbage()
{
check(!IsRooted());

AtomicallySetFlags(RF_MirroredGarbage);
GUObjectArray.IndexToObject(InternalIndex)->SetGarbage();

// If we explicitly marked the object as garbage, remove the async flag so it's visible to the GC
AtomicallyClearInternalFlags(EInternalObjectFlags::Async);
}

对应的IsPendingKill判断也改成了IsValid:

inline bool UKismetSystemLibrary::IsValid(const UObject* Object)
{
return ::IsValid(Object);
}

文末,再次感谢南京周润发的分享, 作者主页:https://www.zhihu.com/people/xu-chen-71-65, 如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群: 793972859 )。

近期精彩回顾



特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
光刻胶,接下来怕是要“火”了!日本三巨头突然官宣:10月1日起,面向全球客户的光刻胶价格全线提涨15%。更狠的是,现货价直接跳涨38%

光刻胶,接下来怕是要“火”了!日本三巨头突然官宣:10月1日起,面向全球客户的光刻胶价格全线提涨15%。更狠的是,现货价直接跳涨38%

扶苏聊历史
2026-09-17 15:07:35
没想到,在敬一丹追悼会上,62岁朱军竟因一个举动实现口碑暴涨

没想到,在敬一丹追悼会上,62岁朱军竟因一个举动实现口碑暴涨

寒士之言本尊
2026-09-17 13:56:19
俄罗斯外长表态!美国已经同意举行中美俄三方战略会谈,但俄罗斯尊重中国的自主决定!

俄罗斯外长表态!美国已经同意举行中美俄三方战略会谈,但俄罗斯尊重中国的自主决定!

扶苏聊历史
2026-09-17 17:45:56
博主向王鹤棣道歉:为自己的无心之失郑重道歉,因操作疏忽误建了负面黑词条,初衷绝对没有抹黑、诋毁的任何恶意……目前该账号已禁言

博主向王鹤棣道歉:为自己的无心之失郑重道歉,因操作疏忽误建了负面黑词条,初衷绝对没有抹黑、诋毁的任何恶意……目前该账号已禁言

极目新闻
2026-09-16 20:34:35
“儿子去世孙子疑非亲生”案再审听证会结束,事实认定与法律适用成关注焦点|封面深镜

“儿子去世孙子疑非亲生”案再审听证会结束,事实认定与法律适用成关注焦点|封面深镜

封面新闻
2026-09-17 15:13:02
iPhone 18 Pro屏下组件藏不住:肉眼可见 强迫症直呼受不了

iPhone 18 Pro屏下组件藏不住:肉眼可见 强迫症直呼受不了

快科技
2026-09-17 16:35:04
“这跟内衣有啥区别?”中学女儿的外出穿搭,让母亲绷不住了

“这跟内衣有啥区别?”中学女儿的外出穿搭,让母亲绷不住了

泽泽先生
2026-09-15 13:34:57
A股调整到3876点,若无意外,明天周五,9月18日,很可能这样走了

A股调整到3876点,若无意外,明天周五,9月18日,很可能这样走了

虎哥闲聊
2026-09-17 15:07:32
郑钦文武网正赛递补第7位!直接晋级几无悬念,这些选手大概率退赛

郑钦文武网正赛递补第7位!直接晋级几无悬念,这些选手大概率退赛

一枚野球君
2026-09-17 21:27:03
深夜,歹徒闯入家中对她施暴近两个小时,她咬紧牙关一声不吭,只为保护楼上熟睡的女儿。当警察破门而入时,才发现这位母亲早已浑身是血

深夜,歹徒闯入家中对她施暴近两个小时,她咬紧牙关一声不吭,只为保护楼上熟睡的女儿。当警察破门而入时,才发现这位母亲早已浑身是血

人生录
2026-07-30 00:05:11
他俩是敬一丹弟弟!如今身居高位,手足四人名字细品大有乾坤

他俩是敬一丹弟弟!如今身居高位,手足四人名字细品大有乾坤

九号探秘人
2026-09-17 20:22:35
十一高铁票涨价引热议 有车次比平时贵近百元!12306回应

十一高铁票涨价引热议 有车次比平时贵近百元!12306回应

快科技
2026-09-17 17:50:09
韩媒:日本亚运会表演出现“丰臣秀吉”引韩方强烈不满,日本亚组委回应

韩媒:日本亚运会表演出现“丰臣秀吉”引韩方强烈不满,日本亚组委回应

环球网资讯
2026-09-17 11:49:06
彻底撕破脸!华为与赛力斯分家,根源正是当年上汽的“灵魂说”

彻底撕破脸!华为与赛力斯分家,根源正是当年上汽的“灵魂说”

叮当当科技
2026-09-17 04:29:55
49岁陈建州血管堵90%突发心梗!两个致命错误,很多人年轻人都在犯

49岁陈建州血管堵90%突发心梗!两个致命错误,很多人年轻人都在犯

消化石医生
2026-09-17 19:49:23
成都一网友称携带油纸伞坐地铁被拦下,地铁工作人员回应:属易燃易爆物品,禁止携带进站

成都一网友称携带油纸伞坐地铁被拦下,地铁工作人员回应:属易燃易爆物品,禁止携带进站

扬子晚报
2026-09-17 20:47:16
邮报:恩佐离队内幕相关报道关窗后才出现,曼城对此感到困惑

邮报:恩佐离队内幕相关报道关窗后才出现,曼城对此感到困惑

懂球帝
2026-09-17 21:53:25
演员许晴分享满满一桌早餐:“我几乎天天都吃这样的早餐”

演员许晴分享满满一桌早餐:“我几乎天天都吃这样的早餐”

韩小娱
2026-09-15 09:48:27
本·拉登藏了9年都没被发现!5米高墙加铁丝网严防死守,没想到最后因晾衣绳上13件长袍和4条尿布暴露

本·拉登藏了9年都没被发现!5米高墙加铁丝网严防死守,没想到最后因晾衣绳上13件长袍和4条尿布暴露

磊子讲史
2026-09-14 17:39:07
董卿和朱军穿黑衣,一起送别敬一丹,康辉紧皱着眉头,徐俐擦眼泪

董卿和朱军穿黑衣,一起送别敬一丹,康辉紧皱着眉头,徐俐擦眼泪

东方不败然多多
2026-09-18 00:07:37
2026-09-18 00:36:49
侑虎科技UWA incentive-icons
侑虎科技UWA
游戏/VR性能优化平台
1623文章数 987关注度
往期回顾 全部

科技要闻

影视飓风Tim反掰iPhoneDuo被质疑

头条要闻

深圳商铺楼板坍塌致1名收废品人员死亡 调查报告公布

头条要闻

深圳商铺楼板坍塌致1名收废品人员死亡 调查报告公布

体育要闻

逆转朝鲜,国足亚运队“啃老”过关

娱乐要闻

rapper赵涛恋情曝光!带甜馨妈妈散步

财经要闻

缺钱的追觅 隐藏的债务?

汽车要闻

猛士X700内饰公布 打造家庭泛越野智能座舱

态度原创

数码
房产
本地
教育
军事航空

数码要闻

Bose Ultra Open Earbuds夹耳式无线耳机发布,售价299美元

房产要闻

突发!三亚安居房出台新政!

本地新闻

不止胖东来!许昌藏着半部三国史

教育要闻

都叫七中不是一所学校成都家长最容易认错的集团校一篇分清

军事要闻

韩国外长表态:尚未决定是否向霍尔木兹海峡派兵

无障碍浏览 进入关怀版