音乐网站设计,宁波专业网站seo公司,58同城长沙回收网站建设,新闻发布会视频前方高能预警#xff0c;新手慎入#xff01;不听劝阻者#xff0c;轻则郁闷堆积#xff0c;重则生死看淡#xff0c;对编程失去了念想#xff0c;对生活失去了幻想#xff01;好了#xff0c;心理强大到NB的可以忽略前方若干警示。为了探索.NET对象的内存分配和回收销… 前方高能预警新手慎入不听劝阻者轻则郁闷堆积重则生死看淡对编程失去了念想对生活失去了幻想好了心理强大到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.Taskc
00007ffc77c1ca70 1 24 System.IO.StreamNullStream
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大小为16Mserver GC模式segment大小为64M。Gen 0和Gen 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虚拟内存到物理内存由页映射使用页表进行管理无法映射到物理内存会导致页失败错误操作系统控制页映射转换有很多技术实现更快的转换比如页表缓存、TLBTranslation Lookaside Buffer技术等。4.2 物理页是怎么组织的当计算机启动后Windows操作系统把来自DRAM的物理页整理为一个列表当有进程需要物理页分配时它转变为WSWorking Set的一部分当一个物理页从WS移除后它通过软件页故障或硬页故障返回到列表硬页故障是非常耗时的因此我们需要避免它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
000002470000c0c0 0000000000000000 00007ffc77c13798
000002470000c0d0 0000010000000080其内部大致的流程如下精简版注意缓存是非常快的以下是来自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会被告知在这个例子中JITUser 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. 小结经过本次的多次深入刨析你对你的对象是不是更加了解了都看到这了还在乎点个赞吗都点赞了还在乎一个收藏吗都收藏了还在乎一个评论吗