如何追踪内存溢出异常?

3
堆栈看起来像这样
:7576b9bc KERNELBASE.RaiseException + 0x58
:671c57ad ; F:\invariant data - not DropBox\3rd_party_vcl\MAD collection\madExcept\Dlls\madExcept32.dll
:671c953f ; F:\invariant data - not DropBox\3rd_party_vcl\MAD collection\madExcept\Dlls\madExcept32.dll
:71a80013 
System._ReallocMem(???,???)
:0040497d @ReallocMem + $45
System._DynArraySetLength
IdIOHandler.TIdIOHandler.ReadFromSource(True,-2,True)
IdIOHandler.TIdIOHandler.ReadLn(#$A,-1,16384,$D3B6FF0)
IdIOHandler.TIdIOHandler.ReadLn(nil)
IdCmdTCPServer.TIdCmdTCPServer.ReadCommandLine($7CC1AFB4)
IdCmdTCPServer.TIdCmdTCPServer.DoExecute($7CC1AFB4)
IdContext.TIdContext.Run
IdTask.TIdTask.DoRun
IdThread.TIdThreadWithTask.Run
IdThread.TIdThread.Execute
:004ccf91 HookedTThreadExecute + $2D
System.Classes.ThreadProc($7A486F84)
System.ThreadWrapper($7BFBEFF8)
:004cce73 CallThreadProcSafe + $F
:004ccee0 ThreadExceptFrame + $3C
:7559339a kernel32.BaseThreadInitThunk + 0x12
:76f39ef2 ntdll.RtlInitializeExceptionChain + 0x63
:76f39ec5 ntdll.RtlInitializeExceptionChain + 0x36

这里有一些代码不是我写的。看起来像是INDY的代码,但如果我的代码有bug,那么异常可能仍然会在其他地方被抛出,导致内存被占满。

我正在使用带有泄漏检测的MAD Except。如果我运行一段时间并关闭程序,则不会报告任何泄漏。如果我让程序运行几个小时,就会出现内存不足的异常。

我只调用了两次Create(),都在定时器处理程序中,并将定时器持续时间设置为一秒钟以进行压力测试。处理程序非常简单,总是Free()创建的对象。

除了定时器处理程序的代码之外,还有其他可以查看的内容吗?

以下是代码,如果有人真的需要看它...这些是我Create()对象的唯一两个地方...

procedure TMainForm.ServerAliveTimerTimer(Sender: TObject);

   var timestamp : LongInt;
       ADConnection: TADConnection;
       theDialogForm : TDialogFormForm;
begin
   ServerAliveTimer.Enabled := False;
   TraceInfo('ServerAliveTimer expired after ' + IntToStr(ServerAliveTimer.Interval div 1000) + ' seconds');

   try
      ADConnection := TADConnection.Create(Self);
      ADConnection.DriverName := 'mysql';
      ADConnection.Params.Add('Server=' + MAIN_STOREROOM_IP_ADDRESS);
      ADConnection.Params.Add('Database=XXX');
      ADConnection.Params.Add('User_Name=XXX');
      ADConnection.Params.Add('Password=XXX');
      ADConnection.Params.Add('Port=3306');
      ADConnection.Connected := True;

   except
      on E : Exception do
      begin
         StopAllTimers();

         TraceError('Database error. Failed to create ADO connection');

         ADConnection.Free();

         theDialogForm := TDialogFormForm.Create(Nil);
         theDialogForm.ShowTheForm('Database problem'+#13+#10+''+#13+#10+
                                   E.ClassName+#13+#10+
                                   E.Message);

         StopTheApplication();
         Exit;
      end;
   end;

   if isMainStoreRoom then
   begin
      CheckIfStoreRoomIsAlive(SECONDARY_STOREROOM_IP_ADDRESS);
   end
   else
   begin
      CheckIfStoreRoomIsAlive(MAIN_STOREROOM_IP_ADDRESS);
   end;

   // Now, update our own timestamp
   try
      timestamp  := GetCurrentUnixTimeStamp();
      ADConnection.ExecSQL('UPDATE server_status SET alive_timestamp="' + IntToStr(timestamp) + '" WHERE ip_address="' + ipAddress + '"');

   except
      on E : Exception do
      begin
         TraceError('Database error. Failed to upate timestamp for ip_address = "' +
                        ipAddress + ' in table "server_status"' + #13#10#13#10 +
                        E.ClassName+#13+#10+
                        E.Message);
         ADConnection.Free();
         Exit;
      end;
   end;

   ADConnection.Free();
   ServerAliveTimer.Enabled := True;
end;     // ServerAliveTimerTimer()

并且

procedure TMainForm.CheckEndOfScheduleTimerTimer(Sender: TObject);
   var ADConnection : TADConnection;
       ADQuery : TADQuery;
       secondsSinceMidnight : LongInt;
       timeNow : LongInt;
       today : LongInt;
       checkoutDay : LongInt;
       checkoutExpireTime : LongInt;
       theDialogForm : TDialogFormForm;
       rfidTag : String;
       i : integer;
begin
   CheckEndOfScheduleTimer.Enabled := False;

   ADConnection := Nil;
   ADQuery := Nil;

   try
      TraceInfo('CheckEndOfScheduleTimer expired after ' + IntToStr(CheckEndOfScheduleTimer.Interval div 1000) + ' seconds');

      secondsSinceMidnight := GetSecondsSinceMidnight();
      timeNow := GetCurrentUnixTimeStamp();
      today := timeNow - secondsSinceMidnight;

      ADConnection := TADConnection.Create(nil);
      ADConnection.DriverName := 'mysql';
      ADConnection.Params.Add('Server=' + MAIN_STOREROOM_IP_ADDRESS);
      ADConnection.Params.Add('Database=XXX');
      ADConnection.Params.Add('User_Name=XXX');
      ADConnection.Params.Add('Password=XXX');
      ADConnection.Params.Add('Port=3306');
      ADConnection.Connected := True;

      ADQuery := TADQuery.Create(ADConnection);
      ADQuery.Connection := ADConnection;
      ADQuery.Open('SELECT * FROM tagged_chemicals');
      ADQuery.FetchAll();

      for i := 0 to Pred(ADQuery.Table.Rows.Count) do
      begin
         if ADQuery.Table.Rows[i].GetData('checked_out') = 'N' then
            Continue;

         checkoutDay        := ADQuery.Table.Rows[i].GetData('checkout_day');
         checkoutExpireTime := ADQuery.Table.Rows[i].GetData('checkout_expire_time');

         if (today + secondsSinceMidnight) > (checkoutDay + checkoutExpireTime) then
         begin
            rfidTag := ADQuery.Table.Rows[i].GetData('rfid_tag');

            TraceInfo('End of pouring time for RFID tag (' + IntToStr(secondsSinceMidnight) + ' seconds after midnight');

            ADConnection.ExecSQL('UPDATE tagged_chemicals ' +
                                    'SET checked_out="N", ' +
                                        'checkout_day="0", ' +
                                        'checkout_expire_time="0" ' +
                                 ' WHERE  rfid_tag="' + rfidTag + '"');
         end;
      end;

      ADQuery.Free();
      ADConnection.Free();

   except
      On E: Exception do
      begin
         ADQuery.Free();
         ADConnection.Free();
         TraceError('Databse exception (' + E.ClassName + ') : "' + E.Message + '"');

         theDialogForm := TDialogFormForm.Create(Nil);
         theDialogForm.ShowTheForm('Database error when checking end of pouring time'+#13+#10+''+#13+#10+
                    E.ClassName+#13+#10+
                    E.Message);
      end;
   end;

   CheckEndOfScheduleTimer.Enabled := True;
end;     // CheckEndOfScheduleTimerTimer()

1
你确定在ServerAliveTimerTimer的两个try..except之间没有未处理的异常吗?特别是在调用CheckIfStoreRoomIsAliveisMainStoreRoom(不确定这个是函数还是布尔值)时。 - Guillem Vicens
4
Indy 正试图分配内存以存储它从套接字中读取的任何内容。也许套接字的另一端发送了一个声称过度庞大的请求,你的程序会试图为其分配全部内存。这可能是另一个程序的 bug,或者该程序恶意地(并成功地)尝试崩溃你的程序。你可以使用 Wireshark 查看套接字上到达的数据。 - Rob Kennedy
3
尝试使用 Sysinternals 工具 VMMap 和 Process Explorer,这将告诉您内存去了哪里。 - Mike Taylor
1
+1 这个问题很有趣,可以看到其他开发人员如何处理这些事情 - 即使只是为了幸灾乐祸的价值 :-) - Leonardo Herrera
1
@Mike,那个评论应该作为一个答案。它可能不能解决特定的问题,但这没关系,因为如果那是问题,它会被认为是太局限而被关闭。你的评论回答了如何跟踪内存不足问题的问题。 - Rob Kennedy
显示剩余2条评论
1个回答

2

尝试使用Sysinternals工具VMMap和Process Explorer,这将告诉您内存去了哪里。


+1 我还没有找到它的位置,但VMMap是一个令人印象深刻的工具。谢谢 - Mawg says reinstate Monica
我从未彻底追踪它,仅进行了一些代码更改和重建,问题就解决了。但VMMap是一个很好的答案。 - Mawg says reinstate Monica

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接