【问题标题】:Is the memory not reclaimed for Delphi apps running on Windows Server 2008 (sp1)?是否没有为在 Windows Server 2008 (sp1) 上运行的 Delphi 应用程序回收内存?
【发布时间】:2009-04-23 02:19:04
【问题描述】:

我们有一个 D2007 应用程序,它在 Windows Server 2008(x64,sp1)上运行时内存占用量稳定增长。
它在 Windows Server 2003(x32 或 x64)、XP 等上运行正常...按预期上升和下降。
我们已经尝试使用包含的内存管理器或最新的 FastMM4 4.92,结果相同。

有没有人试过在 Win2008 上监控任何 Delphi 应用程序的内存使用情况并确认?
或者会有什么线索?

精度:
- 在常识中没有内存泄漏(是的,我对 FastMM 等人非常熟悉)
- 使用 Process Explorer 监控内存使用情况;虚拟内存(私有字节)和物理内存(WorkingSet Private)在 Win2008 上都在增长
- 即使存在内存压力,内存消耗仍在增长。 (这就是我们来调查的原因,因为它导致了失败,但仅限于 Win2008 机器)

更新: //** 改写的 **// 代码比我们的应用程序简单得多,但表现出相同的行为。
创建一个包含 10,000,000 个对象和 10,000,000 个接口的列表,执行 2 次后,在 Windows Server 2008 上执行 100 次后,已用内存增加了约 60MB 和大约 300MB,但只是返回到它在 XP 上的位置。
如果您启动多个实例,则不会释放内存以允许其他实例运行。相反,页面文件会变大,服务器会爬...

更新 2:见 QC report 73347
经过进一步调查,我们已将其追踪到关键部分,如下面的代码所示。
将该代码放入带有 Button 的简单 VCL 应用程序中。并使用 Process Explorer 进行监控:
它从 ~2.6 MB 开始,经过 5 次运行(点击按钮)后,它保持在 ~118.6MB。
在 5 次执行中丢失了 116MB。

//***********************
const
  CS_NUMBER = 10000000;
type
  TCSArray = Array[1..CS_NUMBER] of TRTLCriticalSection;
  PCSArray = ^TCSArray;

procedure TestStatic;
var
  csArray: PCSArray;
  idx: Integer;
begin
  New(csArray);

  for idx := 1 to length(csArray^) do
    InitializeCriticalSection(csArray^[idx]);

  for idx := 1 to length(csArray^) do
      DeleteCriticalSection(csArray^[idx]);

  Dispose(csArray);
end;

procedure TestDynamic(const Number: Integer);
var
  csArray: array of TRTLCriticalSection;
  idx: Integer;
begin
  SetLength(csArray, Number);

  for idx := Low(csArray) to High(csArray) do
    InitializeCriticalSection(csArray[idx]);

  for idx := Low(csArray) to High(csArray) do
      DeleteCriticalSection(csArray[idx]);
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
  ReportMemoryLeaksOnShutdown := True;
  TestStatic;
  TestDynamic(CS_NUMBER);
end;

【问题讨论】:

  • 您指的是私有字节、虚拟大小还是工作集?从 SysInternals 运行 Process Explorer 以监视内存,以便更好地了解正在发生的事情。 technet.microsoft.com/en-us/sysinternals/bb896653.aspx
  • 如果不调用 TestMemoryInterfaces,能否重现问题?
  • 好吧,我问了这个,因为你通过了 MyList.Add(TInterfaceList.Create())。离开构造函数时,接口的引用计数为零。所以,可能会有“坏事”的地方(对不起,我手头没有德尔福来验证这个猜测)。我确实看到了关于这个问题的 QC 报告:用户抱怨类似代码中可能存在的隐藏错误。解决方法是使用显式变量: I := TInterfaceList.Create(); MyList.Add(I);
  • 感谢大家的不同想法。查看新的更新 2 和示例 [ple 代码
  • 你找到解决这个问题的方法了吗?

标签: delphi memory memory-leaks windows-server-2008 critical-section


【解决方案1】:

有一个名为 VMMap 的新 sysinternals 工具可以可视化分配的内存。也许它可以告诉你什么是大内存块。

【讨论】:

    【解决方案2】:

    实际上,Microsoft 对关键部分进行了更改以添加一些调试信息。此调试内存在应用程序结束时才会释放,但会以某种方式缓存和重用,这就是为什么在一段时间后它会趋于平稳。

    如果您想创建大量关键部分而不会感受到这种内存损失,解决方案是修补 VCL 代码,以调用 InitializeCriticalSectionEx 并传递标志 CRITICAL_SECTION_NO_DEBUG_INFO 以避免创建调试结构。

    【讨论】:

      【解决方案3】:

      您是否将 FastMM 包含在完整调试模式中?只需将 FastMM4 单元直接包含在您的项目中并设置

      ReportMemoryLeaksOnShutdown := True
      

      如果没有报告任何内容,则程序退出时可能所有内容都已正常释放(可能是因为引用计数)。您可以使用AQTime 实时监控内存。使用此应用程序,您可以看到每个类名和其余已用内存的字节“计数”。也许你可以看到谁在使用内存。限时演示版足以胜任这项工作。

      【讨论】:

      • 没有泄漏,除了 Windows Server 2008 外运行正常。所以它必须在 MM 和 OS 之间。
      • 当应用程序不泄漏时,AQTime 将不会显示递增数字。我会尝试一下,以确保它不是您的应用程序。
      • 您如何解释相同的代码适用于除 Server 2008 之外的所有 Windows 版本?如果操作系统可以为您的 EXE 添加内存泄漏,那会很有趣。
      • API 调用中可能有细微的变化,这可能会在结构中查找以前保留的字段并以不同的方式解释它们。我在切换到新版本的 windows 时发现了几个问题(虽然我不记得有任何内存泄漏)。
      • 就是这样!他们(微软)改变了关键部分的行为,这很糟糕!谢谢!
      【解决方案4】:

      您指的是私有字节、虚拟大小还是工作集?运行Process Explorer from SysInternals 来监控内存,以便更好地了解正在发生的事情。

      我对此没有任何具体经验(虽然我运行的是 2008 x64 SP1,因此可以对其进行测试),但我建议您创建一个分配大量内存然后释放它的测试应用程序。运行Process Explorer from SysInternals监控内存。

      如果您测试的应用程序重现了相同的行为,请尝试通过在另一个进程中分配内存来产生一些内存压力 - 除非回收之前在第一个进程中释放的内存,否则它将失败。

      如果仍然失败,请尝试使用其他内存管理器。也许是 FastMM 做的。

      【讨论】:

      • 做了一个测试应用。确实对记忆施加了压力。必须尝试使用​​另一个内存管理器......(糟透了!)
      • 所以测试应用有相同的行为?
      • 是的。追踪到 Microsoft 实施关键部分的变化。请参阅更新 2。谢谢!
      【解决方案5】:

      检查您是否有this issue(这是另一个问题,与我在 cmets 中提到的问题无关)。

      【讨论】:

        【解决方案6】:

        我编写了这段代码来纠正我的应用程序中的这个问题。 与 FastCode 的情况相同,要使修复运行,您必须将该单元作为项目的第一个单元。 就像这种情况下的 uRedirecionamentos 一样:

        unit uCriticalSectionFix;
        // By Rodrigo F. Rezino - rodrigofrezino@gmail.com
        
        interface
        
        uses
          Windows;
        
        implementation
        
        uses
          SyncObjs, SysUtils;
        
        type
          InitializeCriticalSectionExProc = function(var lpCriticalSection: TRTLCriticalSection; dwSpinCount: DWORD; Flags: DWORD): BOOL; stdcall;
        
        var
          IsNewerThenXP: Boolean;
          InitializeCriticalSectionEx: InitializeCriticalSectionExProc;
        
        type
          PJump = ^TJump;
          TJump = packed record
            OpCode: Byte;
            Distance: Pointer;
          end;
        
          TCriticalSectionHack = class(TSynchroObject)
          protected
            FSection: TRTLCriticalSection;
          public
            constructor Create;
          end;
        
        function GetMethodAddress(AStub: Pointer): Pointer;
        const
          CALL_OPCODE = $E8;
        begin
          if PBYTE(AStub)^ = CALL_OPCODE then
          begin
            Inc(Integer(AStub));
            Result := Pointer(Integer(AStub) + SizeOf(Pointer) + PInteger(AStub)^);
          end
          else
            Result := nil;
        end;
        
        procedure AddressPatch(const ASource, ADestination: Pointer);
        const
          JMP_OPCODE = $E9;
          SIZE = SizeOf(TJump);
        var
          NewJump: PJump;
          OldProtect: Cardinal;
        begin
          if VirtualProtect(ASource, SIZE, PAGE_EXECUTE_READWRITE, OldProtect) then
          begin
            NewJump := PJump(ASource);
            NewJump.OpCode := JMP_OPCODE;
            NewJump.Distance := Pointer(Integer(ADestination) - Integer(ASource) - 5);
        
            FlushInstructionCache(GetCurrentProcess, ASource, SizeOf(TJump));
            VirtualProtect(ASource, SIZE, OldProtect, @OldProtect);
          end;
        end;
        
        procedure OldCriticalSectionMethod;
        asm
          call TCriticalSection.Create;
        end;
        
        { TCriticalSectionHack }
        
        const
          CRITICAL_SECTION_NO_DEBUG_INFO = $01000000;
          NEW_THEN_XP = 6;
        
        constructor TCriticalSectionHack.Create;
        begin
          inherited Create;
          if IsNewerThenXP then
            InitializeCriticalSectionEx(FSection, 0, CRITICAL_SECTION_NO_DEBUG_INFO)
          else
            InitializeCriticalSection(FSection);
        end;
        
        procedure AdjustMethod;
        var
          LKernel32: HModule;
        begin
          if IsNewerThenXP then
          begin
            LKernel32 := LoadLibrary('kernel32.dll');
            @InitializeCriticalSectionEx := GetProcAddress(LKernel32, 'InitializeCriticalSectionEx');
          end;
        end;
        
        initialization
          AddressPatch(GetMethodAddress(@OldCriticalSectionMethod), @TCriticalSectionHack.Create);
          IsNewerThenXP := CheckWin32Version(NEW_THEN_XP, 0);
          AdjustMethod;
        
        
        end.
        

        【讨论】:

          【解决方案7】:

          除了Alexander,通常这被称为“堆碎片”。

          请注意,FastMM 总体上应该更具弹性和更快,但如果原始应用程序针对 D7 内存管理器进行了调整,FastMM 实际上可能性能更差。

          【讨论】:

            【解决方案8】:

            好吧,即使您的应用程序中没有内存泄漏,内存使用量也会增加。在这些情况下,您可能会泄漏其他资源。例如,如果您的代码分配了一个位图,尽管它释放了所有对象,但设法忘记了最终确定一些 HBITMAP。

            FastMM 会告诉您应用程序中没有内存泄漏,因为您已释放所有对象和数据。但是您仍然会泄漏其他类型的资源(在我的示例中 - GDI 对象)。泄露其他类型的资源也会影响你的记忆。

            我建议您尝试其他工具,它不仅可以检查内存泄漏,还可以检查其他类型的泄漏。我认为 AQTime 有能力做到这一点,但我不确定。

            此行为的另一个可能原因是内存碎片。假设您分配了 2000 个大小为 1 Mb 的对象(让我们暂时忘记 MM 开销和用户空间中其他对象的存在)。现在您拥有完整的 2 Gb 繁忙内存。现在,假设您释放了所有偶数对象,所以现在您已经“剥离”了内存空间,其中混合了 1 Mb 繁忙和空闲块。尽管您现在确实有 1 Gb 的空闲内存,但是您无法为任何 2Mb 对象分配内存,因为空闲块的最大大小仅为 1 Mb(但您确实有 1000 个这样的块;))。 如果内存管理器为您的对象使用了大于 1 Mb 的块,那么当您释放偶数对象时,它无法将内存块释放回操作系统:

            [ [busy] [free] [busy] [free] [busy] [free] ]
            [ [busy] [free] [busy] [free] [busy] [free] ]...
            

            那些大的 [...] 块是半忙的,所以 MM 不能把它们交给操作系统。如果您要求另一个块,大于 1 Mb,那么 MM 将需要从 OS 分配另一个块:

            [ [busy] [free] [busy] [free] [busy] [free] ]
            [ [busy] [free] [busy] [free] [busy] [free] ]...
            [ [your-new-object] [free.................] ]
            

            请注意,这些只是增加内存使用量的示例,尽管您没有内存泄漏。我并不是说你有确切的情况:D

            【讨论】:

            • 除 Server 2008 以外的所有 Windows 版本,相同代码的寻址如何?
            • 为什么不呢?如果代码使用某种在 2008/Vista 中更改的实现细节,那为什么不呢?我的意思是你的代码和 Delphi 的 MM。
            • 这不是内存碎片的问题,而是地址空间碎片的问题。
            • 这可能取决于您如何定义“内存”。我没说是物理内存吧?
            猜你喜欢
            • 2012-04-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-03-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多