오랫동안 내 서버 응용 프로그램의 Win64 버전에서 메모리 누수가 발생했습니다. Win32 버전은 상대적으로 안정적인 메모리 풋 프린트로 잘 작동하지만 64 비트 버전에서 사용되는 메모리는 명백한 이유없이 20Mb/일 정도의 속도로 정기적으로 증가합니다 (말할 필요도없이 FastMM4는 두 메모리 누수를보고하지 않았습니다) . 소스 코드는 32 비트와 64 비트 버전이 동일합니다. 이 응용 프로그램은 Indy TIdTCPServer 구성 요소를 기반으로 구축되었으며 Delphi XE2로 만든 다른 클라이언트가 보낸 명령을 처리하는 데이터베이스에 연결된 고도로 다중 스레드 된 서버입니다.스레드 종료 중 Win64 Delphi RTL에서 메모리 누수가 발생합니까?
나는 내 자신의 코드를 검토하고 왜 64 비트 버전이 너무 많은 메모리를 유출했는지 이해하려고 노력한다. DebugDiag 및 XPerf와 같은 메모리 누수를 추적하도록 설계된 MS 도구를 사용하여 결국 스레드가 DLL에서 분리 될 때마다 일부 바이트가 유출되는 원인이되는 Delphi 64bit RTL에 근본적인 결함이있는 것으로 보입니다. 이 문제는 특히 재시작하지 않고 24/7로 실행해야하는 고도의 멀티 스레드 응용 프로그램에 중요합니다.
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 바이트의 증가, 도시:
Memory usage for the 32 bit version http://img401.imageshack.us/img401/6159/soleak32.png
64 비트 이진 (820K에서 13296K까지) 12,476 바이트의 증가를 나타내고, 더 많은 문제가 : 힙 메모리
Memory usage for the 64 bit version http://img12.imageshack.us/img12/209/soleak64.png
상수의 증가는 XPerf 의해 확인된다
,617,451두 번째 누출 외모 : 무슨 일이 일어 났는지 이해
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 :
는 DebugDiag를 사용하여 나는 누수 된 메모리를 할당 된 코드 경로를 볼 수 있었다 더 확실한 버그 같아. 스레드 종료 중에 StartThreadTLS()를 호출하여 ExitThreadTLS()를 호출하면 은 호출 스레드의 TLS 메모리 블록을 해제 한 다음 Halt0()을 에 호출하여 ExitDll()을 호출하여 에 걸린 예외를 발생시킵니다. DelphiExceptionHandler ( )은 threadvar 변수 ExceptionObjectCount에 액세스 할 때 간접적으로 GetTls()를 호출하고 InitThreadTLS()를 호출하는 AllocateRaiseFrame()을 호출합니다.이 프로세스는 여전히 종료되고있는 프로세스에있는 호출 스레드의 TLS 메모리 블록을 다시 할당합니다. 따라서 StartLib()은 DLL_THREAD_DETACH 동안 Halt0()을 호출해서는 안되며, _TExitDllException이 발생하면 DelphiExceptionHandler는 이 AllocateRaiseFrame()을 호출하지 않아야합니다.
스레드 종료를 처리하는 Win64 방식에는 중대한 결함이 있음이 분명합니다. 이러한 동작은 Win64에서 27/7을 실행해야하는 다중 스레드 서버 응용 프로그램의 개발을 금지합니다.
그래서 :
- 당신은 무엇을 나의 결론에 대해 어떻게 생각하십니까?
- 이 문제의 해결 방법이 있습니까?
테스트 소스 코드 및 바이너리 can be downloaded here.
귀하의 기여에 감사드립니다!
: QC Report 105559을 편집하십시오. 귀하의 투표를 기다리고 있습니다 :-)
"이 문제에 대한 해결책이 있습니까?"나는 다음의까지 32 비트 어플리케이션을 사용할 것입니다. 안정적인 64 비트 컴파일러로 델파이를 출시했습니다 ... –
ComputerSaysNo
만약 내가 당신이라면, 최소한의 크기로 샘플을 잘라내어 누출을 보여 주며 단순히 QC에 제출하십시오. –
@DorinDuminica : 그때 델파이 XE4가 될 것입니다.) – whosrdaddy