21

在下面的程序中,尽管执行了垃圾回收,但不会重新获得初始内存大小的大小。1. 初始内存大小为 Total memory: 16,940 bytes Private bytes 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

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

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

4

5 回答 5

25

总体问题

尝试计算和报告内存使用情况的处理方式是占用比正在测量的 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 调用。

性能和诊断工具 - 屏幕截图

罢工之子 - 笔记

可以在VS2010中使用SoS,只要你的机器上没有安装.Net 4.5,也可以在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
于 2013-10-06T02:22:05.367 回答
3

我有以下结果:

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() 不会直接释放资源!

于 2013-09-11T12:37:46.437 回答
2

总内存: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();
    }
于 2013-09-11T12:04:56.080 回答
2

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

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 字节。

于 2013-10-09T20:36:07.580 回答
2

GC.Collect 是异步的,所以在 GC.Collect() 之后直接调用 DisplayMemory() 的逻辑;是有问题的。 同步运行 GC.Collect有一些关于 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

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

于 2013-09-11T11:40:57.367 回答