使用正确的工具进行调试

当你需要调试/调查问题时,我想指出的一个非常强大的工具是你的调试器(如果你的调试器也是windbg/cdb,那就是对了,因为这就是我要用的,这也是我在本文中将要讨论的)。
对于那些对研究内存相关问题感兴趣的人来说,无论是因为你不喜欢当前应用程序的内存使用情况,还是只是想提高,学习使用调试器都是非常宝贵的。如果您还没有开始使用windbg/cdb,我强烈建议您–我保证您不会后悔的。
我以前说过使用SoS,在V4.0中添加了更多的SoS命令,其中一些与GC相关,像!AnalyzeOOM!GCWhere 和 !FindRoots。你可以在MSDN页面上看到它们。但我想谈谈一些从阅读参考资料中看不到的技巧。
当你的程序是GC时,你想知道什么是最常见的两件事吗?

1) 为什么触发GCs
2) 为什么GCs要花这么多时间?

答案分别是:

1) GC前后堆的差异
2) 那个GC的幸存者

让我详细解释一下。每一次都有自己的分配预算,我在这里解释了这一点。这是一个我们设置的值,当超过这个值时,我们想要触发一个GC。如果我们做了一个收集,发现有很多内存存留下来,我们会把这个分配预算设置得非常大,这意味着您需要为该代分配更多的内存,这样我们才能触发GC。其基本原理是,这样我们下次进行GC时就有机会回收一些相当大的空间。否则,我们将为GC做所有这些工作,而不能找到太多的死内存。
所以在GC之前,如果你运行 !dumpheap,你可以看到你有什么对象;然后在GC之后你在运行 !dumpheap,你会看到其中一些对象消失了。正是这些对象触发了这个GC。为什么?因为如果我们没有任何物体消失,我们就不会做GC(因为我们不会得到死内存)。
到目前为止,CLR-GC还没有压缩LOH。因此,如果你想看看LOH,你可以确切地看到内存的哪些部分消失了。下面是gen2gc前后堆范围的示例输出。在gen2gc之前(我格式化了!dumpheap,将methodtable指针替换为可读名称的)

Address              MethodTable          Size

————————————————–

00000007f7ffe1e0     System.Byte[]        2,080,000

00000007f81f9ee0     System.String        100,000

00000007f8212580     System.Byte[]        140,000

 

After this gen2 GC for the same heap range:

 

Address              MethodTable          Size

————————————————–

00000007f7ffe1e0     Free                 2,320,000

所以这3个物体被收集起来了。作为应用程序的作者,这些对象很可能会为您敲响警钟,告诉您为什么要分配它们,这样您就可以从那里了解是否/如何进行一些优化。集合中的幸存者就是你在!dumpheap输出中看到的在那个GC的末尾。
我经常看到人们很快就去寻找其他一些工具,他们真的可以快速地调出调试器并设置一些断点来看看事情是什么样子的。当然,有很多有用的工具存在——它们倾向于为你聚合信息,这样你就不必自己去做了——但有时只使用最直接、最方便的工具,即调试器,会非常方便。
另一个典型的例子是当人们要求我帮助调试内存泄漏时,最简单、最快的解决方案是在 kernel32!VirtualAlloc上设置一个条件断点。现在很清楚是什么导致了内存泄漏。

如果您需要看到某些命令的连续输出,而不需要过多地干扰进程,则可以将其记录到一个文件(.logopen c:\mydebugsession.txt)并在进程运行一段时间后分析日志。
正如我提到的,有一些工具可以为您聚合这些信息(例如,您可以在上找到的DebugDiag工具,它显示了所有VirtualAlloc调用的聚合调用堆栈),但如果您从未使用过这些工具,则可能需要一段时间来设置它们并学习如何使用它们。调试器就是你手头上的东西,你可以随时使用它,所以你可以很方便地用调试器来解决它,我倾向于用它。

 

posted on 2020-09-03 07:53  活着的虫子  阅读(296)  评论(0编辑  收藏  举报

导航