【问题标题】:Finding Memory leaks in C#在 C# 中查找内存泄漏
【发布时间】:2013-09-15 09:37:37
【问题描述】:

在下面的程序中,虽然执行了垃圾回收,但内存的初始大小不会重新获得。 1.内存的初始大小是 总内存:16,940 字节 私有字节 8134656

  1. 在循环内创建对象,以便在循环外执行 gc collect 时释放这些对象,因为这些对象没有其范围。

  2. 但是GC收集后的内存和初始大小不一样 总内存:29,476 字节 私有字节 8540160 处理数:115

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MemoryLeakTest
{

    class Program
    {
        static void DisplayMemory()
        {
            Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));            
            Console.WriteLine("Private bytes {0}", System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64);
            Console.WriteLine("Handle count: {0}", System.Diagnostics.Process.GetCurrentProcess().HandleCount);
            Console.WriteLine();
        }

        static void Main()
        {
            DisplayMemory();
            GC.Collect();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("--- New object #{0} ---", i + 1);

                object o = new object();

                GC.Collect();
                DisplayMemory();
            }
            Console.WriteLine("--- press any key to quit ---");            
            Console.WriteLine();
            Console.Read();

            GC.Collect();
            DisplayMemory();
        }

    }
}

Output: 
=======
Total memory: 16,940 bytes
Private bytes 8134656
Handle count: 115

--- New object #1 ---
Total memory: 25,588 bytes
Private bytes 8540160
Handle count: 115

--- New object #2 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- New object #3 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- New object #4 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- New object #5 ---
Total memory: 25,576 bytes
Private bytes 8540160
Handle count: 115

--- press any key to quit ---


Total memory: 29,476 bytes
Private bytes 8540160
Handle count: 115

*********************************

私有字节和托管堆大小从初始大小增加的原因是什么?

【问题讨论】:

  • 当 .NET 运行时保留内存时,当它不再实际使用该内存时,它不会立即归还 - 也不应该。
  • 这个之前在 Stack Overflow 上讲过:stackoverflow.com/questions/1343374/…
  • 如果私有字节像这样增加和保留,那么其他进程就不能使用这个内存,因为私有字节不共享。对吗?
  • 令我震惊的是,您完全问错了问题。您应该问为什么总内存会下降或保持不变,即使您分配了对象。显然,内存管理远不止表面上所见,许多关于 .NET 的书籍都对此进行了很好的介绍。
  • 是的,我对私有字节和堆大小的异常增加\减少感到困惑

标签: c# .net memory-management garbage-collection


【解决方案1】:

总体问题

尝试计算和报告内存使用情况的处理方式所占用的内存是所测量的 GC 对象分配大小的 10,000 倍。

此外,打印句柄计数不适用于手头的问题(因为在测试中没有打开/关闭句柄),但它确实会导致大量内存分配(仅删除该计数会减少总分配一半)。

原始程序试图测量 60-120 字节对象的分配(取决于它是 32 位还是 64 位程序),但它使用的函数会导致每次分配 600 KB 内存调用,其中一半在大对象堆 (LOH) 上。

提供了另一种测试方法,它表明所有对象在 GC.Collect 调用之后确实消失了。还提供了有关 DisplayMemory 函数的内存使用情况的详细信息。

结论

当创建并收集 100k 个对象时,托管内存大小不会增加。当只创建和收集 5 个对象时,进程的私有字节增加了大约 12 KB,但 SoS 显示它不是来自托管堆。当您处理非常小的尺寸和对象数量时,您将无法准确确定发生了什么;相反,我建议使用非常多的对象进行测试,以便很容易查看是否有东西泄漏。在这种情况下,没有泄漏,没有任何问题,一切都很好。

分析工具和方法

我使用了两个工具来查看这个程序的内存使用情况:

  1. VS 2013 Pro - 性能和诊断工具 - 我首先运行它,发现原始程序分配了 3.6 MB 内存,而不仅仅是对象分配所期望的 60-120 字节。我知道字符串和写入控制台会占用一些内存,但 3.6 MB 令人震惊。
  2. Son of Strike (SoS) - 这是一个在 Visual Studio 和 WinDbg 中工作的调试器扩展,它随 .Net 框架一起提供(请参阅您机器上每个框架版本目录中的 sos.dll)。

VS 2013 Pro - 性能和诊断工具 - 注释

在 VS 2013 Pro 的性能和诊断工具下运行原始程序的结果,“分析方法”设置为“.NET 内存分配”。这提供了一个非常快速的线索,即分配的内存比想象的要多。请参阅图表上方的 3.6 MB 总分配。如果您删除下降到 2,476 字节的 DisplayMemory 调用。

罢工之子 - 笔记

只要机器上没有安装.Net 4.5就可以在VS2010中使用SoS,也可以在VS2012中使用Update3;只需确保在您的项目中启用非托管调试并确保您正在启动 32 位进程,然后在 VS 调试器的即时窗口中运行“.load sos”。我用来查看此问题的命令是“!eeheap -gc”和“!dumpheap -stat”。

替代测试程序

class Program
{
    static void Main()
    {
        // A few objects get released by the initial GC.Collect call - the count drops from 108 to 94 objects in one test
        GC.Collect();

        // Set a breakpoint here, run these two sos commands:
        // !eeheap -gc
        // !dumpheap -stat
        for (int i = 0; i < 100000; i++)
        {
            object o = new object();
        }

        // Set a breakpoint here, run these two sos commands before this line, then step over and run them again
        // !eeheap -gc
        // !dumpheap -stat
        GC.Collect();
    }
}

替代测试结果

总结

分配和收集 100,000 个 System.Objects 后,我们最终得到的对象比开始时少 4 个,托管堆大小比开始时小 900 字节。

垃圾收集按预期工作。

基线 - 在第一次 GC.Collect 之后

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x024f23d0
generation 1 starts at 0x024f100c
generation 2 starts at 0x024f1000
ephemeral segment allocation context: none
         segment             begin         allocated  size
024f0000  024f1000  024f23dc  0x13dc(5084)
Large object heap starts at 0x034f1000
         segment             begin         allocated  size
034f0000  034f1000  034f5380  0x4380(17280)
Total Size:              Size: 0x575c (22364) bytes.
------------------------------
GC Heap Size:    Size: 0x575c (22364) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed026b8        1          112 System.AppDomain
6ed025b0        2          168 System.Threading.ThreadAbortException
6ed05d3c        1          284 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed03a6c        2          380 System.Int32[]
6ed0349c       20          560 System.RuntimeType
0047fab8       14         1024      Free
6ed02248       32         1692 System.String
6ecefe88        6        17340 System.Object[]
Total 95 objects

分配 100,000 个 System.Objects 之后,最终 GC.Collect 之前

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x024f23d0
generation 1 starts at 0x024f100c
generation 2 starts at 0x024f1000
ephemeral segment allocation context: none
         segment             begin         allocated  size
024f0000  024f1000  02617ff4  0x126ff4(1208308)
Large object heap starts at 0x034f1000
         segment             begin         allocated  size
034f0000  034f1000  034f5380  0x4380(17280)
Total Size:              Size: 0x12b374 (1225588) bytes.
------------------------------
GC Heap Size:    Size: 0x12b374 (1225588) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed024e4        1           84 System.OutOfMemoryException
6ed02390        1           84 System.Exception
6ed026b8        1          112 System.AppDomain
6ed025b0        2          168 System.Threading.ThreadAbortException
6ed05d3c        1          284 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed03a6c        2          380 System.Int32[]
6ed0349c       20          560 System.RuntimeType
0047fab8       14         1024      Free
6ed02248       32         1692 System.String
6ecefe88        6        17340 System.Object[]
6ed025e8   100002      1200024 System.Object
Total 100095 objects

在最终 GC.Collect 之后

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x024f2048
generation 1 starts at 0x024f2030
generation 2 starts at 0x024f1000
ephemeral segment allocation context: none
         segment             begin         allocated  size
024f0000  024f1000  024f2054  0x1054(4180)
Large object heap starts at 0x034f1000
         segment             begin         allocated  size
034f0000  034f1000  034f5380  0x4380(17280)
Total Size:              Size: 0x53d4 (21460) bytes.
------------------------------
GC Heap Size:    Size: 0x53d4 (21460) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed024e4        1           84 System.OutOfMemoryException
6ed02390        1           84 System.Exception
6ed026b8        1          112 System.AppDomain
0047fab8        9          118      Free
6ed025b0        2          168 System.Threading.ThreadAbortException
6ed05d3c        1          284 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed03a6c        2          380 System.Int32[]
6ed0349c       20          560 System.RuntimeType
6ed02248       32         1692 System.String
6ecefe88        6        17340 System.Object[]
Total 91 objects

查看 DisplayMemory 函数的内存使用情况

与 System.Object 分配相比,DisplayMemory 占用了大量内存。它正在创建字符串(在堆上),它调用来获取内存的函数本身正在使用大量(大约 600 KB)的内存。

调用 DisplayMemory 之前的内存使用情况

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x02321018
generation 1 starts at 0x0232100c
generation 2 starts at 0x02321000
ephemeral segment allocation context: none
         segment             begin         allocated  size
02320000  02321000  02323ff4  0x2ff4(12276)
Large object heap starts at 0x03321000
         segment             begin         allocated  size
03320000  03321000  03325380  0x4380(17280)
Total Size:              Size: 0x7374 (29556) bytes.
------------------------------
GC Heap Size:    Size: 0x7374 (29556) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed05d3c        3          468 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][]
6ed0349c       20          560 System.RuntimeType
6ed02248       38         2422 System.String
6ecefe88        6        17340 System.Object[]
Total 102 objects

调用 DisplayMemory 后的内存使用情况

!eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x023224fc
generation 1 starts at 0x023224f0
generation 2 starts at 0x02321000
ephemeral segment allocation context: none
         segment             begin         allocated  size
02320000  02321000  02371ff4  0x50ff4(331764)
Large object heap starts at 0x03321000
         segment             begin         allocated  size
03320000  03321000  033653c0  0x443c0(279488)
Total Size:              Size: 0x953b4 (611252) bytes.
------------------------------
GC Heap Size:    Size: 0x953b4 (611252) bytes.

!dumpheap -stat
Statistics:
      MT    Count    TotalSize Class Name
[...]
6ed02c08        9          954 System.Char[]
006dfac0       17         1090      Free
6ed03aa4      156         1872 System.Int32
6ecffc20      152         3648 System.Collections.ArrayList
6ed05ed4        9         7776 System.Collections.Hashtable+bucket[]
7066e388      152        16416 System.Diagnostics.ProcessInfo
6ed02248      669        20748 System.String
706723e4      152        29184 System.Diagnostics.NtProcessInfoHelper+SystemProcessInformation
6ecefe88      463        48472 System.Object[]
706743a4     2104        75744 System.Diagnostics.ThreadInfo
70666568     2104       151488 System.Diagnostics.NtProcessInfoHelper+SystemThreadInformation
6ed0d640        2       262168 System.Int64[]
Total 6132 objects

【讨论】:

    【解决方案2】:

    我有以下结果:

    Total memory: 94.804 bytes
    Private bytes 19230720
    Handle count: 252
    
    --- New object #1 ---
    Total memory: 96.932 bytes
    Private bytes 19820544
    Handle count: 252
    
    --- New object #2 ---
    Total memory: 96.932 bytes
    Private bytes 19820544
    Handle count: 252
    
    --- New object #3 ---
    Total memory: 96.932 bytes
    Private bytes 19820544
    Handle count: 252
    
    --- New object #4 ---
    Total memory: 96.932 bytes
    Private bytes 19820544
    Handle count: 252
    
    --- New object #5 ---
    Total memory: 96.932 bytes
    Private bytes 19820544
    Handle count: 252
    
    --- press any key to quit ---
    Total memory: 96.920 bytes
    Private bytes 19820544
    Handle count: 252
    

    class Program
    {
    static void DisplayMemory()
    {
        Console.WriteLine("Total memory: {0:###,###,###,##0} bytes", GC.GetTotalMemory(true));
        Console.WriteLine("Private bytes {0}", System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64);
      Console.WriteLine("Handle count: {0}", System.Diagnostics.Process.GetCurrentProcess().HandleCount);
        Console.WriteLine();
     }
    
     static void Main()
     {
      DisplayMemory();
      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();
    
       for (int i = 0; i < 5; i++)
       {
         Console.WriteLine("--- New object #{0} ---", i + 1);
    
         object o = new object();
    
         GC.Collect();
         GC.WaitForPendingFinalizers();
         GC.Collect();
    
         DisplayMemory();
       }
    
       Console.WriteLine("--- press any key to quit ---");
      //Console.ReadLine();
    
      GC.Collect();
      GC.WaitForPendingFinalizers();
      GC.Collect();
    
      GC.WaitForFullGCComplete();
      DisplayMemory();
    
      Console.ReadLine();
    }
    

    我刚刚删除了Console.ReadLine(); 并添加了GC.WaitForPendingFinalizers();。 现在,您希望的代码似乎是 Console.ReadLine() 不会直接释放资源!

    【讨论】:

    • 如果您将总内存和私有字节的初始大小与它们的最后值进行比较,您仍然可以找到增量。为什么 GC 不能正确释放内存?
    • @user186246 但是您在控制台中仍然有一些数据,并且创建了一些对象,我们需要分析器来分析这个周末问题出在哪里,我会用分析器检查它..
    【解决方案3】:

    总内存:160,144 字节 私有字节 27189248 处理数:247

    --- 新对象 #1 --- s 总内存:160,152 字节 私有字节 27979776 处理数:247

    --- 新对象 #2 --- s 总内存:160,152 字节 私有字节 27979776 处理数:247

    --- 新对象 #3 --- 总内存:160,152 字节 私有字节 27979776 处理数:247

    --- 新对象 #4 --- s 总内存:160,152 字节 私有字节 27979776 处理数:247

    --- 新对象 #5 --- s 总内存:160,152 字节 私有字节 27975680 处理数:247

    --- 按任意键退出---

    总内存:160,152 字节 私有字节 27996160 处理数:247

        static void Main()
        {
            DisplayMemory();
            GC.Collect();
            GC.WaitForFullGCComplete();
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine("--- New object #{0} ---", i + 1);
    
                object o = new object();
    
                GC.Collect();
                GC.WaitForFullGCComplete();
                Console.Read();
                Console.Read();
                DisplayMemory();
    
            }
            Console.WriteLine("--- press any key to quit ---");
            Console.WriteLine();
            Console.Read();
            Console.Read();
    
            GC.Collect();
            GC.WaitForFullGCComplete();
            DisplayMemory();
        }
    

    【讨论】:

      【解决方案4】:

      GC.Collect 是异步的,所以在 GC.Collect() 之后直接调用 DisplayMemory() 的逻辑;是有问题的。 Run GC.Collect synchronously 有一些关于 GC.WaitForPendingFinalizers() 的细节。

      编辑 - 回复您的问题:

      Total memory: 84,280 bytes
      Private bytes 15384576
      Handle count: 245
      
      --- New object #1 ---
      Total memory: 86,408 bytes
      Private bytes 15908864
      Handle count: 245
      
      --- New object #2 ---
      Total memory: 86,408 bytes
      Private bytes 15908864
      Handle count: 245
      
      --- New object #3 ---
      Total memory: 86,408 bytes
      Private bytes 15908864
      Handle count: 245
      
      --- New object #4 ---
      Total memory: 86,408 bytes
      Private bytes 15908864
      Handle count: 245
      
      --- New object #5 ---
      Total memory: 86,408 bytes
      Private bytes 15908864
      Handle count: 245
      

      所以这里似乎并没有太大的不同!

      【讨论】:

      • 我尝试了同步 GC 收集,即使发现相同的结果。你能运行程序并检查一下吗?
      • 已添加输出 - 这里似乎没有太大区别!:)
      • 你缺少最后一部分:---按任意键退出---总内存:xxxxxx字节私有字节xxxxxxxxx句柄计数:xx
      【解决方案5】:

      我测试了您的代码。您得到的前两个输出如下:

      Output: 
      =======
      Total memory: 16,940 bytes
      Private bytes 8134656
      Handle count: 115
      
      --- New object #1 ---
      Total memory: 25,588 bytes
      Private bytes 8540160
      Handle count: 115
      

      这里的内存使用量显着增加,这是因为在 DisplayMemory() 方法中使用了静态类 Console (Console.WriteLine)。这不是因为您在循环中多次创建对象。循环中的对象创建仅创建 120 字节的总内存,但使用 Console 类大约使用 8000 字节的总内存。试试下面的代码,你就会明白了。

      static void Main()
              {
                  DisplayMemory();
                  List<object> objList = new List<object>();
                  for (int i = 0; i < 15; i++)
                  {
                      Console.WriteLine("--- New object #{0} ---", i + 1);
      
                      object o = new object();
                                  objList.Add(o);
                      DisplayMemory();
                  }
      
                  GC.Collect();
      
      
                  DisplayMemory();
                  Console.WriteLine("--- press any key to quit ---");
      
                  Console.ReadLine();
              }
      

      观察,每次创建对象,总内存增加 120 字节。

      【讨论】:

        猜你喜欢
        • 2017-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-18
        相关资源
        最近更新 更多