3

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

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

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

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

更新 2:见QC 报告 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;
4

8 回答 8

3

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

于 2009-04-23T17:48:00.627 回答
3

实际上,微软对关键部分进行了更改以添加一些调试信息。这个调试内存直到应用程序结束才被释放,而是以某种方式缓存和重用,这就是为什么一段时间后它可以稳定下来的原因。

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

于 2011-08-05T21:32:23.403 回答
1

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

ReportMemoryLeaksOnShutdown := True

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

于 2009-04-23T08:57:50.150 回答
1

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

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

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

如果仍然失败,请尝试使用不同的内存管理器。也许是 FastMM 在做这件事。

于 2009-04-23T19:08:14.927 回答
1

检查您是否有此问题(这是另一个问题,与我在对您问题的评论中提到的问题无关)。

于 2009-04-25T07:33:57.233 回答
1

我做了这段代码来纠正我的应用程序中的这个问题。与 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.
于 2011-08-04T22:28:04.743 回答
0

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

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

于 2009-04-25T22:35:51.840 回答
-1

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

于 2009-04-24T07:39:29.210 回答