热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

串联的Delphi字符串是否保存在保留对字符串的引用的隐藏临时变量中?

如何解决《串联的Delphi字符串是否保存在保留对字符串的引用的隐藏临时变量中?》经验,为你挑选了1个好方法。

我试图了解Delphi服务器应用程序中的内存问题:最初我怀疑彻底泄漏,但是现在相信,由于编译器在使用+动态连接字符串时使用了隐藏的临时文件,因此内存的闲置时间比应有的更长。 ,造成痛苦的自由空间内存碎片。

背景:

这是Windows上的一整套32位服务器应用程序,Delphi版本已经很老了,我认为它是7,但可以肯定是Unicode之前的版本,并使用Nexus 3内存管理器在其中编写了DLL来挂接所有分配/ free调用(以及GB的内存跟踪)。

我有应用程序源代码,但没有编译器;我不是该应用程序的开发人员(甚至不是Delphi开发人员),但创建了广泛的自定义工具来监视,跟踪和分析内存。我一直在IDA Pro反汇编程序中选择.EXE。

一些示例代码:

我试图将这种情况减少到最低限度。此代码无意于编译:

procedure TaskThread.RunWorkLoop
begin
    while not Terminated do
    begin

      tsk := WaitForWorkToDo();  // this could sit for minutes at a time

      SetThreadName('Working on ' + tsk.Name);

      tsk.Run(); // THIS COULD TAKE A LONG TIME

      SetThreadName('Idle');
   end
end;

SetThreadName() 接受一个const字符串参数并将其挂起,以便系统的其他部分知道此线程在做什么。

我对代码的反汇编显示,编译器已分配了一个隐藏的本地临时变量,以接收“工作中”部分和任务名称部分的串联,这是传递给的SetThreadName地方,它还保留了字符串的句柄。

当任务正在运行时-可能是20分钟-我相信该字符串有两个句柄。一个保存在内部SetThreadName,另一个保存在隐藏的临时文件中。

一切都很好。

然后,当任务结束且线程名称设置为时'Idle'SetThreadName()释放原始字符串并分配文字Idle

但是:我相信隐藏的本地临时文件仍然保留该字符串的句柄,且refcount = 1,因此它将占用空间,直到过程返回或下一循环来覆盖该隐藏的本地临时文件,释放旧值。

在此期间,程序无法访问它,无法将其显式释放,并且没有用处,但仍在消耗内存。

对于大多数过程而言,这无关紧要,因为它们的开始和结束都相对接近,因此所有内容都可以一次发布,但是在循环服务器应用程序中,这些过程可能会停留更长的时间。这导致我们内存碎片。

变得更糟

在实际的应用程序中,它更像是:

SetThreadName(tsk.Name + '-' + FormatDateTime('mm/dd/yy hh:nn:ss', Now));

在这种情况下,有两个隐藏的临时变量:一个临时变量用于的结果,另一个临时FormatDateTime变量用于整体串联的结果,实际上是这样运行的:

tmp1: String;
tmp2: String;
...
  tmp1 := FormatDateTime('...');
  tmp2 := tsk.Name + '-' + tmp1;
  SetThreadName(tmp2);

我敢肯定FormatDateTime,在任务完成后很长一段时间里,我就已经看到字符串结果在内存中徘徊了,我已经看到它实际上是一个大约30字节的分配,位于一个1兆字节的内存部分的中间,周围是可用空间; Nexus3MM用于VirtualAlloc分配更大的OS级块。

这单30字节的字符串最终被释放,无论是在下一循环或当程序退出,所以我敢肯定这不是一个泄漏,但我宁愿是一个30字节的分配坐在寂寞的一个中间完成后,兆字节部分实际上就消失了,因此整个部分都可以发布到操作系统。

但是,如果它停留的时间足够长,则内存管理器将从中分配其他内容,并且内存中的此漏洞将变得更加永久。

我们有非常详细的忙/闲内存映射,并确保这种碎片正在杀死我们(这当然不是唯一的原因)。

我的问题:

1)我理解正确吗?

2)如果是这样,则是通过使用显式临时表消除隐藏临时表的唯一解决方法,其中我们将执行以下操作:

tmp1: String;
tmp2: String;
...
  tmp1 := FormatDateTime('...');
  tmp2 := tsk.Name + '-' + tmp1;
  SetThreadName(tmp2);
  tmp1 := '';  // release the date/time string
  tmp2 := '';  // release the overall thread name string

我非常有信心我必须使用FormatDateTime中间结果(我已经看到了)来完成此操作,但是不确定整体串联。

这只是感觉不对。

编辑:几周后才更新。我们已经重写了中央循环以使用显式的临时性,实际上它在某些关键服务器进程的内存碎片化方面产生了明显的(尽管不是主要的)差异。我们还有其他事情需要研究,但是对我来说很明显,这是一条值得走的路。



1> Ken Bourassa..:

根据我的经验,它确实可以像这样工作。我不确定这是通过合同还是通过实施。我猜想随着最近增加的内联变量声明,现在可能有所不同。但是我相信在preunicode的Delphi中,它的工作方式完全符合您的描述。

所有使用托管类型的变量(隐式或显式)或包含一个变量的记录try/finally的例程都将在例程中生成一个隐式块,其中finally一部分将清除引用。您的代码真正的作用是:

procedure TaskThread.RunWorkLoop
var
  sImplicit : string;
begin
  sImplicit := '';
  try
    while not Terminated do
    begin
      tsk := WaitForWorkToDo();  // this could sit for minutes at a time

      sImplicit := 'Working on ' + tsk.Name;

      SetThreadName(sImplicit);

      tsk.Run(); // THIS COULD TAKE A LONG TIME

      SetThreadName('Idle');
    end;
  finally
    sImplicit := '';
  end;
end;

在您的情况下,由于您永远不会退出使用隐式变量的例程,因此它确实会保留在内存中。

至于解决方案,我相信您的建议会奏效。但是您也可以将代码简单地移动到另一个方法(或本地过程)。

procedure TaskThread.RunWorkLoop
  procedure JustKeepWorking;
  begin
    tsk := WaitForWorkToDo();  // this could sit for minutes at a time
    SetThreadName('Working on ' + tsk.Name);
    tsk.Run(); // THIS COULD TAKE A LONG TIME
    SetThreadName('Idle');
  end;
begin
  while not Terminated do
  begin
    JustKeepWorking;
  end
end;

另外,您可能需要检查此问题以获取其他见解。


推荐阅读
author-avatar
冷尔岚钰_820
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有