当前位置: 首页 > news >正文

CLR、托管、非托管 之四——补充内容

在 unsafe 代码或与非托管代码交互时,使用 GCHandle.Alloc(obj, GCHandleType.Pinned) 可以固定托管对象在内存中的位置(防止 GC 移动它),并获取其地址(一个 IntPtr)。这在需要将托管对象的地址传递给非托管函数时非常关键。”

这里有一个非常核心的问题————GC为什么要移动它,触及了.NET垃圾回收(GC)机制的核心设计之一。

GC移动托管对象的原因主要是为了优化内存使用、提高性能并避免内存碎片

让我们深入分解:

1. 内存压缩(Compaction)

*   **过程:** GC 在回收不再使用的对象(垃圾)后,托管堆上会出现“空洞”(即被释放对象占据的空间)。此时,GC 会将所有存活的对象**向堆的低地址端移动**,紧密地排列在一起,消除这些空洞。
*   **目的:***   **避免内存碎片:** 如果没有压缩,频繁的分配和回收会导致堆上散布着大量大小不一、不连续的空闲内存块(外部碎片)。虽然单个空闲块的总和可能很大,但当需要分配一个较大的连续内存块时,可能找不到足够大的连续空闲空间,即使总空闲空间足够。压缩消除了这些空洞,将空闲空间合并成一个大的连续块。*   **提高分配速度:** 压缩后,新的对象分配变得极其简单快速。内存分配器只需要一个简单的指针(称为“下一个对象指针”或“分配指针”)指向当前堆的末尾。分配新对象时,只需移动这个指针即可(称为“指针碰撞”分配)。这比在碎片化的堆中搜索合适大小的空闲块要快得多。

2. 分代垃圾回收(Generational Garbage Collection)

*   **设计:** .NET GC 将托管堆划分为三代:第 0 代(Gen 0)、第 1 代(Gen 1)和第 2 代(Gen 2)。新创建的对象都在 Gen 0。经历一次 GC 后仍然存活的对象会“晋升”到 Gen 1。Gen 1 存活的对象再经历 GC 会晋升到 Gen 2。
*   **移动与分代:***   **Gen 0 和 Gen 1 的回收最频繁:** 基于“弱代假说”(大部分对象都是短命的),GC 会优先且更频繁地回收 Gen 0 和 Gen 1。这些回收**几乎总是伴随着压缩**。因为年轻代对象小、回收频繁,压缩带来的性能提升(快速分配,避免碎片)远大于移动对象的开销。*   **Gen 2 回收(完整 GC):** 回收频率较低。它*可能*进行压缩,也可能不进行(取决于碎片程度、GC 模式等策略)。移动 Gen 2 的大量长期存活对象开销很大,所以 GC 会权衡是否压缩。
*   **移动的必要性:** 分代回收依赖于能够快速识别和移动存活对象到更高的代。压缩是实现这种代际晋升的基础机制之一。

3. 移动带来的好处总结

*   **内存利用率高:** 消除碎片,最大化利用可用内存。
*   **分配速度极快:** 指针碰撞分配是几乎零成本的(只需移动指针和可能的零初始化)。
*   **局部性提升:** 紧密排列的对象可能具有更好的缓存局部性(CPU 缓存命中率更高),提升程序运行速度。
*   **简化内存管理:** 相对于非托管内存管理(需要复杂的内存分配器如 malloc/free 来处理碎片),托管堆的管理变得非常简单高效。

4. 移动带来的问题与 GCHandle.Pinned 的由来

*   **问题:** 当托管代码需要将一个托管对象(如数组、结构体)的**内存地址**传递给非托管代码(通过 P/Invoke)时,就产生了冲突。非托管代码会持有一个指向该对象在托管堆中位置的指针(地址)。
*   **风险:** 如果在非托管代码使用该指针期间(例如,在调用一个需要较长时间完成的非托管函数过程中,或者非托管代码在后台线程异步操作时),GC 发生了,并且移动了该对象,那么非托管代码持有的指针就变成了一个**悬垂指针**。它指向的可能是:*   一个已经被回收的垃圾对象的位置。*   一个现在存放着完全不同对象的位置。*   一片空闲内存。
*   **后果:** 访问这样的指针会导致不可预测的行为:数据损坏、程序崩溃(访问违规)或安全漏洞。这是极其严重的问题。
*   **解决方案:`GCHandle.Pinned`***   **固定(Pinning):** 使用 `GCHandle.Alloc(yourObject, GCHandleType.Pinned)` 告诉 GC:“这个对象非常重要,在它的 `GCHandle` 被释放 (`Free()`) 之前,**无论发生多少次垃圾回收,都不要移动它!**”*   **获取稳定地址:** `GCHandle.AddrOfPinnedObject()` 返回该对象被固定后的内存地址 (`IntPtr`)。这个地址在固定期间是**稳定不变**的,可以安全地传递给非托管代码使用。*   **责任:** 开发者必须确保:1.  在非托管代码**使用完**这个指针之后,**立即调用** `GCHandle.Free()` 来解除固定。2.  固定时间**尽可能短**。长期固定对象会阻碍 GC 压缩该对象所在的内存区域,导致该区域产生**内存碎片**。如果大量对象或大对象被长期固定,会显著降低 GC 效率,增加内存占用,甚至可能引发 `OutOfMemoryException`。

总结

  • GC移动对象是为了: 高效利用内存(消除碎片)、加速新对象分配(指针碰撞)、提升缓存局部性,并支持分代回收策略。
  • 移动是常态: 尤其是在频繁回收的年轻代(Gen 0/1),压缩(伴随移动)是标准操作。
  • 与非托管交互的冲突: 非托管代码持有托管对象的指针时,GC 移动对象会导致该指针失效(悬垂指针),引发灾难性后果。
  • GCHandle.Pinned 的作用: 是一种明确的指令,要求 GC 在固定期间不移动特定对象,从而提供一个稳定的内存地址供非托管代码安全使用。开发者必须谨慎管理固定的生命周期(及时释放)以避免长期固定导致的内存碎片问题。

理解 GC 移动对象的动机和机制,以及 GCHandle.Pinned 如何解决与非托管交互的冲突,对于编写安全、高效、可互操作的 .NET 代码至关重要。

http://www.sczhlp.com/news/18682/

相关文章:

  • 合肥建站费用百度爱采购推广怎么收费
  • wordpress菜单添加首页南京百度快照优化排名
  • 集团网站开发企业网站定制开发
  • 企业网站建设好的案例aso优化排名违法吗
  • 做环境设计的网站项目网站
  • 网站开发的技术总结郑州计算机培训机构哪个最好
  • 网站的投资和建设项目今日头条新闻视频
  • 万网虚拟主机上传网站seo兼职接单平台
  • 河北省建设厅官方网站重庆seo排名公司
  • 网站 无限下拉北京十大最靠谱it培训机构
  • 仁怀哪儿做网站网络营销品牌公司
  • 网站文案案例网站排名查询
  • 北京建站方案关键词查网址
  • 网站安全防护找谁做论文关键词
  • 扬州网站制作网推项目接单平台
  • 无忧网站建设搜索引擎优化策略包括
  • 公司网站建设考核免费游戏推广平台
  • 带icp备案的网站产品如何做网络推广
  • 嵌入式学习DAY6 C语言05 - 实践
  • 企业智能化测试体系 与 AI 测试智能体平台设计
  • 功能覆盖率
  • 网站被入侵后需做的检测(1)定制网站和模板建站
  • 网站icp备案怎么查询嘉兴seo网络推广
  • 怎样查看网站是用什么cms_做的seo点击排名器
  • 政府网站建设预算seo百度网站排名软件
  • php做的网站facebook厦门seo排名公司
  • 济南专业的网站建设公司网站按天扣费优化推广
  • 班级网站空间建设取得效果企业推广视频
  • 怎么做企业网站运营soso搜索引擎
  • 网站信息架构百度站长工具怎么用