编程技巧:如何简单获取某个(些)对象的所占用的内存

首页

2014-05-18

cover

(注:封面为《CLR via C#》第4版的插图,此书内容深浅适中,乃.NET领域必读书之一。)

前几天在新浪微博上想要推广我的微信公众账号,还发了二维码,结果基本没什么人来,我还觉得奇怪。后来发现,原来是一旦附带了微信公众账号的二维码图片,新浪微博就会将这条消息在粉丝的首页上隐藏起来。当然,我本人的首页看上去自然一切正常。这简直是岂有此理,这种恶性竞争损害的都是用户的利益,所谓“用户至上”这种口号,在实际操作中都被狗吃了。

现在我将二维码发到了自己的VPS上,再花了一百多买了新浪微博的粉丝头条,这关注者的数量终于上来了一点了,也欢迎各位向自己的朋友推荐一下。

目前第一期的有奖征答活动也还在进行之中,只有少数同学提交了答案,内容也不太理想,大家要抓紧啊。

有人建议我说,微信公众账号还是不要放大段技术内容,吐吐槽写点简单的东西比较合适。现在我也有这种感觉,新浪微博用来随口说说,博客用来认真写写,至于说短不短说长不长的内容,就放到这个公众账号上面吧。

这次我来分享一个小技巧。最近这几条消息都是关于程序内存占用优化的。说起优化,那自然需要评测,需要对比数据,否则优化无从谈起。假如要知道一个或者一系列对象所占用的内存,最好的方法自然是使用一些Memory Profiler,但是一般来说这些工具都有一些门槛,甚至还不够灵活。

假如我们只是要做一些小实验,比如查看某个对象所占用的内存,则完全可以使用接下来我要介绍的方法。假设,我们现在想要知道一下,存放100个随机元素的字典会占用多少内存,便可以这么做:

var array = Enumerable.Range(0, 100).Select(_ => new object()).ToArray();
var preRun = array.ToDictionary(o => o);

var start = GC.GetTotalMemory(true);
var d = array.ToDictionary(o => o);
var end = GC.GetTotalMemory(true);

Console.WriteLine(end - start);

GC.KeepAlive(d);
GC.KeepAlive(preRun);

这里用到了两个平时可能不太常用的方法。第一个是GC.GetTotalMemory(falseFullCollection),用于获得当前堆上所占用的内存,传入true意味着统计前要做一次完整的GC(包括Finalization)。第二个是GC.KeepAlive(object),这个方法其实什么事情都不做,只不过让代码中可以多出一份对此对象的引用,避免被垃圾回收掉。

这个做法的优点的最大好处在于简单,几句话便可以知道自己想要的结果。缺点在于可以知道的东西不多,真正的细节方面还得依靠Memory Profiler。这段代码相比Memory Profiler的另一个好处在于,它能够确切得知这个字典及其内部对象的迟钝,而据我所知那些Memory Profiler似乎没法把其中的那些object也排除掉——它只能得到从字典对象追溯到底的总共尺寸。

此外,使用这个办法还需要注意两点:第一,要小心地在两次GC.GetTotalMemory(true)之间剔除所有的无关对象,例如那100个object就必须事先创建好。第二,被测试的代码需要“预热”一下,如上面这段代码的“preRun”部分。CLR会在第一次运行的时候才会加载一些元数据或静态成员,因此我们要确保被测试的对象中不包含这部分延迟加载的对象。

至于最后的结果是多少,自己运行着看呗。

阅读原文