微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

内存泄漏在Win64 Delphi RTL在线程关闭?

长期以来,我注意到我的服务器应用程序的Win64版本泄漏内存。虽然Win32版本工作正常与相对稳定的内存占用,64位版本使用的内存增加定期 – 也许20Mb /天,没有任何明显的原因(不用说,FastMM4没有报告任何内存泄漏的两个) 。源代码在32位和64位版本之间是相同的。该应用程序是围绕Indy TIdTcpserver组件构建的,它是一个高度多线程的服务器,连接到处理由Delphi XE2生成的其他客户端发送的命令的数据库

我花了很多时间审查自己的代码,并试图了解为什么64位版本泄漏了这么多的内存。我最终通过使用MS工具设计来跟踪内存泄漏,如DebugDiag和XPerf,似乎有一个根本的缺陷在Delphi 64bit RTL,导致一些字节被泄漏每次一个线程已从DLL分离。这个问题对于必须每天24小时运行而不重新启动的高度多线程应用程序特别重要。

我复制了一个非常基本的项目,由主机应用程序和库组成的两个用XE2构建的问题。 DLL与主机应用程序静态链接。主机应用程序创建的线程只是调用虚拟导出过程并退出

这里是库的源代码

library FooBarDLL;

uses
  Windows,System.SysUtils,System.Classes;

{$R *.res}

function FooBarProc(): Boolean; stdcall;
begin
  Result := True; //Do nothing.
end;

exports
  FooBarProc;

主机应用程序使用一个计时器来创建一个调用导出过程的线程:

TFooThread = class (TThread)
  protected
    procedure Execute; override;
  public
    constructor Create;
  end;

...

function FooBarProc(): Boolean; stdcall; external 'FooBarDll.dll';

implementation

{$R *.dfm}

procedure THostAppForm.TimerTimer(Sender: TObject);
begin
  with TFooThread.Create() do
    Start;
end;

{ TFooThread }

constructor TFooThread.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
end;

procedure TFooThread.Execute;
begin
  /// Call the exported procedure.
  FooBarProc();
end;

下面是一些屏幕截图,显示使用VMMap的泄漏(看看红线命名为“堆”)。在30分钟间隔内拍摄以下截图。

32位二进制表示增加了16个字节,这是完全可以接受的:

64位二进制表示增加12476字节(从820K到13296K),这是更有问题:

堆内存的不断增加也由XPerf证实:

使用DebugDiag我能够看到分配泄漏内存的代码路径:

LeakTrack+13529
<my dll>!Sysinit::AllocTlsBuffer+13
<my dll>!Sysinit::InitThreadTLS+2b
<my dll>!Sysinit::::GetTls+22
<my dll>!System::AllocateraiseFrame+e
<my dll>!System::DelphiExceptionHandler+342
ntdll!RtlpExecuteHandlerForException+d
ntdll!RtldispatchException+45a
ntdll!KiUserExceptiondispatch+2e
KERNELBASE!RaiseException+39
<my dll>!System::::RaiseAtExcept+106
<my dll>!System::::RaiseExcept+1c
<my dll>!System::ExitDll+3e
<my dll>!System::::Halt0+54
<my dll>!System::::StartLib+123
<my dll>!Sysinit::::InitLib+92
<my dll>!Smart::initialization+38
ntdll!LdrShutdownThread+155
ntdll!RtlExitUserThread+38
<my application>!System::EndThread+20
<my application>!System::Classes::ThreadProc+9a
<my application>!SystemThreadWrapper+36
kernel32!BaseThreadInitThunk+d
ntdll!RtlUserThreadStart+1d

雷米Lebeau helped me on the Embarcadero forums了解发生了什么:

The second leak looks more like a definite bug. During thread
shutdown,StartLib() is being called,which calls ExitThreadTLS() to
free the calling thread’s TLS memory block,then calls Halt0() to
call ExitDll() to raise an exception that is caught by
DelphiExceptionHandler() to call AllocateraiseFrame(),which
indirectly calls GetTls() and thus InitThreadTLS() when it accesses a
threadvar variable named ExceptionObjectCount. That re-allocates the
TLS memory block of the calling thread that is still in the process
of being shut down. So either StartLib() should not be calling
Halt0() during DLL_THREAD_DETACH,or DelphiExceptionHandler should
not be calling AllocateraiseFrame() when it detects a
_TExitDllException being raised.

看起来很清楚,我有一个重大缺陷在Win64的方式来处理线程关闭。这种行为禁止开发任何必须在Win64下运行27/7的多线程服务器应用程序。

所以:

>你对我的结论有什么看法?
>你有任何一个解决这个问题的解决方法吗?

测试源代码和二进制代码can be downloaded here

感谢您的贡献!

编辑:QC Report 105559.我在等你的投票:-)

解决方法

一个非常简单的工作是重新使用线程,而不是创建和销毁它们。线程是相当昂贵,你可能会得到一个perf提升太…调试虽然… …

原文地址:https://www.jb51.cc/delphi/103735.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐