前方高能预警,新手慎入!不听劝阻者,轻则郁闷堆积,重则生死看淡,对编程失去了念想,对生活失去了幻想!好了,心理强大到NB的可以忽略前方若干警示。为了探索.NET对象的内存分配和回收销毁,您可能需要准备一些调试的基本知识,比如上篇的<利用SOS扩展库进入高阶.NET6程序的调试>.以下例子来自.net 6技术支持。

1. 我们的第一个对象

我们的第一个对象,不是你初中暗恋的古灵精怪的小女孩,更不是你高中的神秘御姐范的初恋女友,她是地地道道的Object。

不信,我Show给你看。

public static int Main()
{MaoniType o = new MaoniType(128, 256);Console.ReadLine();// 其它乱七八糟的代码return 0;
}

掀开她神秘的盖头,她也只不是千千万万普通对象中的一员,非要说她有什么不同的话,那可能就是你想驯服她,并且你花费了你的宝贵时间,在她身上。

public class MaoniType
{public MaoniType(int a, int b){A = a;B = b;}public int A { get; set; }public int B { get; set; }
}

2. 正确的打开她

美丽总是隐藏在朦胧之中,隔纱看美人,越看越迷人。

不过我们需要的不是肤浅的撩骚,让我们利用高级窥探工具,更加深入到灵魂的探索她。

当然,最最简单的探索工具,就是Windbg + SoS 扩展了。

至于工具的使用,不是重点,在这里就略过了,如果你还不会的话,那么就移步<利用SOS扩展库进入高阶.NET6程序的调试>瞧瞧,那里已经给你备好了下酒好菜。

闲话少叙,让我们直接打开工具,键入神秘指令,来个一指入魂吧。

0:007> .load C:\Users\webmote.dotnet\sos\sos.dll
0:007> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
00007ffc77c37598        1           24 System.IO.SyncTextReader
00007ffc77c33478        1           24 System.Threading.Tasks.Task+<>c
00007ffc77c1ca70        1           24 System.IO.Stream+NullStream
00007ffc77c13798        1           24 ConsoleApp6.MaoniType
...
[omitted]00007ffc77bd7f48       28         1160 System.SByte[]
00007ffc77bd8410        4         3596 System.Int32[]
00007ffc77c1d3c8        3         4178 System.Byte[]
00007ffc77b2b578        8        18216 System.Object[]
00007ffc77c33898        3        33356 System.Char[]
00007ffc77bdd698       82        35610 System.String
Total 208 objects

没错,找到 ConsoleApp6.MaoniType 这个类名,这就是你心心念的 对象 No 1.

3. 深入内存

既然已经被你定位到了,那么就让我们继续深入吧, 现在只需要点她的牌牌就可以了。

0:007> !DumpHeap /d -mt 00007ffc77c13798Address               MT     Size
000002470000c0c8 00007ffc77c13798       24     Statistics:MT    Count    TotalSize Class Name
00007ffc77c13798        1           24 ConsoleApp6.MaoniType
Total 1 objects

现在,有了她第一手的资讯:

姓名:Maoni/莫妮
尺寸:24
起点:c0c8 [000002470000c0c8]
个数:1个
表索引:[00007ffc77c13798]

4. 继续深入——内存布局调查

让我们来看看GC地址空间的情况:

0:007> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x0000024700001030
generation 1 starts at 0x0000024700001018
generation 2 starts at 0x0000024700001000
ephemeral segment allocation context: nonesegment             begin         allocated         committed    allocated size    committed size
0000024700000000  0000024700001000  00000247000173C8  0000024700022000  0x163c8(91080)  0x21000(135168)
Large object heap starts at 0x0000024710001000segment             begin         allocated         committed    allocated size    committed size
0000024710000000  0000024710001000  0000024710001018  0000024710002000  0x18(24)  0x1000(4096)
Pinned object heap starts at 0x0000024718001000
0000024718000000  0000024718001000  0000024718005420  0000024718012000  0x4420(17440)  0x11000(69632)
Total Allocated Size:              Size: 0x1a800 (108544) bytes.
Total Committed Size:              Size: 0x22000 (139264) bytes.
------------------------------
GC Allocated Heap Size:    Size: 0x1a800 (108544) bytes.
GC Committed Heap Size:    Size: 0x22000 (139264) bytes.

你应该没忘记我们对象的地址吧?

莫妮的地址是 000002470000c0c8,而新生段的分配信息我们也可以清晰的看到。

当然有关,估计仍然需要一大章节才能说明白吧,这里仅仅简单介绍下。

它是GC从操作系统采集内存的一个单位,实际内存申请和分配以及释放以segment(段)为单位;

例如: workstation GC模式segment大小为16M,server GC模式segment大小为64M。

Gen 0Gen 1 heap总是位于同一个段中,叫做ephemeral segment(新生段),
Gen 2 heap由0个或多个segments组成,LOH由1个或多个segments组成

.NET程序启动时CLR为heap创建2个segment,一个作为ephemeral segment,另一个用于LOH

Full GC后完全空闲的segments将被释放掉,内存返回给操作系统

再次深入前,让我们来点小甜点,放松一下,看看四周的风景。

4.1 我们怎么用DRAM

不管怎么分配,我们都需要涉及到物理内存。

当然,我们并不支持使用物理内存!

我们使用虚拟内存(VM),这块有操作系统的哦VMM(虚拟内存管理器)提供。

操作系统引入了虚拟内存概念,使得我们能够:

  • 每个进程都认为它有自己的内存空间,就好象国家的廉租房制度一样,让每个人都体验到家的温馨。

  • 你可以请求更多的内存,甚至超过了物理内存大小,而管理器只会占用真正使用的物理内存;

  • 重要的是,不需要VM分配为连续的了,实现了即抛即用。

VM的实现也很有意思,由操作系统提供页的支持:

  • 由MMU(内存管理单元)实现

  • 内存被分割为页(一般是4K)

  • 虚拟内存到物理内存由页映射使用页表进行管理

  • 无法映射到物理内存,会导致页失败错误

  • 操作系统控制页映射转换

有很多技术实现更快的转换,比如页表缓存、TLB(Translation Lookaside Buffer)技术等。

4.2 物理页是怎么组织的?

  1. 当计算机启动后,Windows操作系统把来自DRAM的物理页整理为一个列表;

  2. 当有进程需要物理页分配时,它转变为WS(Working Set)的一部分

  3. 当一个物理页从WS移除后,它通过软件页故障或硬页故障返回到列表

  4. 硬页故障是非常耗时的,因此我们需要避免它
    5.为了避免硬页故障,我们不能增加大于物理内存的堆栈(可以观察物理内存负载信息)

4.3 GC怎么从VM采集内存

  • 保留内存

    由于需要分页的原因,因此我们可以请求稍后可能使用的范围地址,它被称作保留内存(VirtualAlloc 使用 MEM_RESERVE)。当然保留内存不能保存任何数据。

  • 提交内存

    当我们需要在页存储上存储数据时,我们告诉操作系统,这叫提交内存。(VirtualAlloc使用MEM_COMMIT),提交操作成功后,保证你不会得到OOM异常。

保留内存操作是非常快的,当然你仍然需要增加一次用户态<–> 内核态的操作;提交内存也是非常快的… 当然,知道你真正的保存数据。而恰恰这个时候,有可能引起分配页故障,导致OOM。

  • 保存数据
    一切都oK了,我们呢就可以轻松保存数据了。

5. 再次深入内存布局调查

让我们回到从前,一如第一次初见。

5.1 初见

假设上图就是我们的段(segment)内存的保留内存(Reserve memory)区域,那么你想到了什么?

是的,首先她是一个空荡荡的巨大空间!

当然,这里面也没有任何东西。

5.2 相识

现在,我们想要在段内存中保存一些东东,该怎么办呢?

是的,我们得混个脸熟!

好了,首先我们需要保存段的头信息,那让我们先提交个申请(通常是64K)。

有了第一次后,我们对这个操作流程应该熟门熟路了,所以,谁也抵挡不住我们前进的脚步。

再次提交存储对象的空间请求(通常是64K),当然,GC通常不会仅仅为一个对象申请内存.

5.3 行动

它通常先申请一个分配上下文,当然这个时候并没有对象被构造。

然后动用物理内存页,保存数据,查看存储信息如下:

0:007> dq 000002470000c0c8-8 l3
00000247`0000c0c0  00000000`00000000 00007ffc`77c13798
00000247`0000c0d0  00000100`00000080

其内部大致的流程如下(精简版):

注意:缓存是非常快的,以下是来自Intel的数据。

  • L1 缓存:4 cpu周期

  • L2 缓存:12 cpu周期

  • L3 缓存:44 cpu周期

DRAM的读取大约 60ns ~ 100ns之间。

5.4 小结下

经过前面不断的深入探索,对象的内存分布已经在你面前完全展开。那么,让我们再总结下。

GC的分配如下:

6. 清扫战场

经过上面让人目眩神秘的命令和图片,你学废了吗?

最后,让我们打扫下战场,看看GC这位小宝贝。

6.1 GC怎么决定收集

如下代码,让我们看看它能有多智能?

public static int Main()
{MaoniType o = new MaoniType(128, 256);GCHandle h = GCHandle.Alloc(o, GCHandleType.Weak);GC.Collect();Console.WriteLine("Collect called, h.Target is {0}",(h.Target == null) ? "collected" : "not collected");return 0;
}

发生了什么?输出是:

Output - Collect called, h.Target is not collected

是的,你没有看错,GC.Collect()收集整个堆栈,这意味着GC不能决定对象的生命周期。
如果一个对象还活着,那么GC会被告知,在这个例子中,JIT(User Roots)告诉GC,对象还活着。因此GC无法回收对象。

6.2 开始收集

好了,让我们来个真正的回收。

[MethodImpl(MethodImplOptions.NoInlining)]public static void TestLifeTime(){MaoniType o = new MaoniType(128, 256);h = GCHandle.Alloc(o, GCHandleType.Weak);}public static int Main(){TestLifeTime();GC.Collect();Console.WriteLine("Collect called, h.Target is {0}",(h.Target == null) ? "collected" : "not collected");return 0;}

输出结果:

Output: Collect called, h.Target is collected

再次观察GC:

是的,GC摧毁了对象,内存回收了。

7. 小结

经过本次的多次深入刨析,你对你的对象是不是更加了解了?

秘境探索之一个.NET 对象从内存分配到内存回收相关推荐

  1. 【慎入】秘境探索之一个.NET 对象从内存分配到内存回收

  2. 一个Java对象到底占用多大内存?

    最近在调研MAT和VisualVM源码实现,遇到一个可疑问题,两者计算出来的对象大小不一致,才有了这样疑惑. 一个Java对象到底占用多大内存? 为了复现这个问题,准备了4个最简单类: class A ...

  3. 一个Java对象到底占多大内存?(转)

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  4. JavaScript内存分配及垃圾回收机制

    JavaScript内存分配及垃圾回收机制 简介 像C语言这样的高级语言一般都有底层的内存管理接口,比如 malloc()和free().另一方面,JavaScript创建变量(对象,字符串等)时分配 ...

  5. C/C++内存分配、内存区划分、常量存储区、堆、栈、自由存储区、全局区(静态区)、代码区

    C/C++内存分配.内存区划分.常量存储区.堆.栈.自由存储区.全局区 C++中,内存分成几个区: 栈(stack) 堆(heap) 自由存储区 全局/静态存储区 常量存储区 代码区 对于C++的内存 ...

  6. 【C进阶】之动态内存分配及内存操作函数

    动态内存分配及内存操作函数 1 动态内存分配的介绍 2 malloc和free函数 3 测试代码 4 goto的使用场合,常用于出错处理 5 memset() 6 memcpy() 7 memcmp( ...

  7. 4 OC 中的内存分配以及内存对齐

    目录 一  OC  中的内存分配 一  OC  中的内存分配 student 结构体明明是20?为什么是24个字节,因为结构体会按照本身成员变量最大的内存进行对齐,最大成员变量是8个字节,因此就是8的 ...

  8. JVM结构、内存分配、垃圾回收算法、垃圾收集器。

    2019独角兽企业重金招聘Python工程师标准>>> 一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知,JVM主要包括四个部 ...

  9. JVM内存分配与垃圾回收浅析

    为什么80%的码农都做不了架构师?>>>    想做architect,就必须对JVM的性能有所了解.JVM的内存管理是性能的一大瓶颈.JVM的性能调优,必须建立在对内存管理策略理解 ...

最新文章

  1. vector 容器 动态数组总结
  2. HDU 1233 还是畅通工程。
  3. 基于以太坊的去中心化宠物商店构建教程
  4. 云计算与分布式计算,网格结算,对等计算,并行计算..的关系
  5. 计算机无法使用光驱启动,电脑BIOS怎么设置光盘启动 三种类型BIOS设置光驱启动的图文详解教程...
  6. NOIP模拟测试34「次芝麻·呵呵呵·长寿花」
  7. python的river安装
  8. 生成多个 SSH-Key 、生成多个公钥和私钥
  9. 通过Small Basic把儿子/女儿带入编程的世界
  10. python登录界面实现密码在明文与星号间切换_两个API让星号密码框显示成明文
  11. Windows结束某个端口的进程
  12. 高炉计算机控制,一高炉计算机控制系统升级.pdf
  13. java怎么从大到小排序元祖_列表、元祖的操作
  14. 多个高危漏洞可导致 Chrome 浏览器被黑
  15. matlab字符串转换
  16. 【转】PLC编程软件: KW multiprog 和 codesys
  17. Input 输入调出数字输入
  18. 动态内存的申请和非动态内存的申请_公安交管新举措咋解读?非营运七座车6年免检,70岁可申请驾照...
  19. 配置freeglut和glew
  20. 基于微信小程序的答题系统

热门文章

  1. HDU 1978 How many ways DP问题
  2. Lync Server 2010迁移至Lync Server 2013部署系列 Part18:开启Lync 2013 Mobility
  3. WriteComponent,ReadComponent
  4. ondestroy什么时候调用_尾调用和尾递归
  5. bindresult必须在哪个位置_手机视频剪辑工具哪个好?清爽视频编辑APP有人推荐吗?...
  6. PHP-Redis扩展安装 error: ext/standard/php_smart_str.h: No such file or directory
  7. Linux环境下设置IPDNSGateway
  8. 只需要2个工具,百度云盘大文件就能用迅雷和IDM下载
  9. CAP理论与MongoDB一致性、可用性的一些思考
  10. Memcached简介