Delphi 2009 - 接口属性会导致内存泄漏吗?

4

我继承了一个Intraweb应用程序,其中有一个2MB的文本文件记录了FastMM4报告的内存泄漏。现在我已经将其减少到了115个实例中的一个类泄漏了52个字节。

这个类的简要描述是:

TCwcBasicAdapter = class(TCwcCustomAdapter)  
  protected  
    FNavTitleField: TField;  
    function GetAdapterNav(aDataSet: TDataSet): ICwcCDSAdapterNav; override;  
  public  
    constructor Create(aDataSource: TDataSource; aKeyField, aNavTitleField: TField; aMultiple: boolean);  
  end;  

接口为:

  ICwcCDSAdapterNav = interface(IInterface)  

如果这个属性是引用计数的,那么我是否在打错方向?在什么情况下接口属性会阻止类被销毁?

下面是上述方法的实现:

function TCwcBasicAdapter.GetAdapterNav(aDataSet: TDataSet): ICwcCDSAdapterNav;
var
  AdapterNav: TCwcCDSAdapterNavBase;
begin
  result := nil;
  if Assigned(aDataSet) then begin
    AdapterNav := TCwcCDSAdapterNavBasic.Create(aDataSet, FKeyField.Index, FNavTitleField.Index);
    try
      AdapterNav.GetInterface(ICwcCDSAdapterNav, result);
    except
      FreeAndNil(AdapterNav);
      raise;
    end;
  end;
end;

将类声明为:

TCwcCDSAdapterNavBase = class(TInterfacedObject, ICwcCDSAdapterNav)

等一下...你在说哪个接口属性?这里没有任何属性。 - Rob Kennedy
我们需要 GetAdapterNav 的代码,以查看对象/接口是如何被创建的。同时也需要调用它的代码,以查看它是如何被处理的。 - mj2008
抱歉,发布函数时出了一些问题。 - user122603
function TCwcBasicAdapter.GetAdapterNav(aDataSet: TDataSet): ICwcCDSAdapterNav;
var
AdapterNav: TCwcCDSAdapterNavBase;
begin
result := nil;
if Assigned(aDataSet) then begin
AdapterNav := TCwcCDSAdapterNavBasic.Create(aDataSet, FKeyField.Index, FNavTitleField.Index);
try
AdapterNav.GetInterface(ICwcCDSAdapterNav, result);
except
FreeAndNil(AdapterNav);
raise;
end;
end;
end; 该类声明为:
TCwcCDSAdapterNavBase = class(TInterfacedObject, ICwcCDSAdapterNav)
- user122603
“坏演员”是指TCwcBasicAdapter正在进行分配,而TCwcCDSAdpaterNavBasic正在泄漏。对吗?(“坏演员”听起来像是FastMM在归咎于某些东西,但实际上并不是这样。FastMM只是告诉您某个东西被分配的位置。我无法告诉您谁负责释放它。) - Rob Kennedy
显示剩余3条评论
3个回答

4

FastMM应该能够告诉您哪些内容泄漏以及它们是在哪里创建的。
这将有助于缩小真正罪犯的范围:谁泄漏了什么?

我不确定您的问题是什么?
您的代码不完整或者不是问题所在的代码:您的类没有Interface属性或Interface私有字段,只有一个返回接口的方法,这是无害的。

编辑:如果我们没有看到实现ICwcCDSAdapterNav的对象的代码,我们就不能确定它是否确实被引用计数了。
如果您没有从TInterfacedObject中派生,那么很可能它没有被引用计数,您不能依赖自动释放内存...

您可能想要查看这个CodeRage 2会话为菜鸟打造的内存泄漏战斗指南。它主要展示了如何使用FastMM来预防/检测Delphi中的内存泄漏。虽然是针对D2007的,但对其他版本仍然适用。


谢谢,我今天上班会看一下你的演示文稿。你在DelphiLive!上被删节了。 - user122603
你有没有一份演示文稿,描述了“传递泄漏容器”幻灯片上列出的一些技术?此外,我下载了你的DelphiLive!演示文稿,但找不到查看它的方法。它缺少文件吗? - user122603
我选择了这个答案,因为所提到的演示帮助我找到了真正的问题,尽管还没有解决。 - user122603

4
到目前为止,关于FastMM如何工作,您已经得到了一些很好的答案。但是对于您实际的问题,是的,接口对象可以以两种不同的方式泄漏。
  1. 只有当它们所属的对象在其_AddRef和_Release方法中实现了引用计数时,接口才会被引用计数。有些对象没有。
  2. 如果您有循环接口引用(接口1引用接口2,接口2引用接口1),则引用计数将永远不会降至0,除非您采取一些特殊的技巧。如果这是您的问题,我将向您介绍Andreas Hausladen最近在博客文章中提到的内容。

谢谢,我怀疑上述第二项正在发生。 - user122603
你还可以检查一些奇怪的用法,例如强制类型转换。要追踪泄漏问题,FastMM确实很有帮助,但我建议你尝试使用AQTime http://www.automatedqa.com/products/aqtime/。我知道它不是免费的,但对于每个泄漏的完整堆栈跟踪的了解肯定会改善清理代码所需的时间。 - Ryan VanIderstine
@Mason- 你还记得那篇文章的名字吗?链接现在已经失效了。 - Gabriel

2
如果您泄漏了115个TCwcBasicAdapter类的实例,那么被泄漏的是这个类本身,而不是它所引用的内容所占用的内存。您的某处有115个TCwcBasicAdapter实例没有被释放。
此外,属性不存储数据,无论它们是接口还是其他类型。只有字段(以及编译器为该类分配的一些隐藏空间)占用内存。
因此,您正在错误地指责此类导致内存泄漏。您的内存泄漏在其他地方。当FastMM告诉您有内存泄漏时,它是否也告诉您每个泄漏实例的分配位置。它具备这种能力;您可能需要调整一些条件编译符号以启用此功能。
当然,并不仅仅是该类的实例在泄漏。FastMM 还应该报告其他一些正在泄漏的事物,例如实现接口的类或类的实例。
根据您添加的函数,我开始怀疑真正泄漏的是TCwcCDSAdapterNavBase,这可能是因为您创建它的方式不太典型。在GetAdapterNav中的异常处理程序是否会运行?我怀疑不会;TObject.GetInterface从不明确引发异常。如果对象不支持接口,则返回False。该异常处理程序能够捕获的仅是诸如访问冲突和非法操作之类的问题,您真的不应该在那里捕获这些异常。
您可以像这样更直接地实现该函数:
if Assigned(FDataSet) then
  Result := TCwcCDSAdapterNavBase.Create(...);

如果你知道是谁创建了泄漏的对象,那通常可以告诉你谁应该负责释放它们。我知道他已经有关于泄漏的日志了。但我建议他可能还没有查看整个日志,因为很不可能只有那个类的实例泄漏了。该类为自己分配的东西可能也泄漏了,以及那些接口引用指向的对象。最后,关键点是接口属性不能成为该类泄漏实例的原因。 - Rob Kennedy
FastMM4 报告提到了泄漏类的创建,其中该类被添加到 TObject 的派生类的一个 TObjectList 成员中。TObjectList 被创建为非拥有模式,并且在类的析构函数中是 FreeAndNil,紧接着是一个 for 循环调用 Remove 从其 Items 中移除。令人沮丧的是,所有这一切都按预期进行。 - user122603
那么,如果TObjectList不拥有那些创建的对象,那么谁拥有它们呢?哪段代码应该负责清理这些对象?你有任何释放它们的代码吗?如果没有,那么添加一些(可能只需使列表拥有这些对象即可)。如果有,则找出为什么该代码未被执行。 - Rob Kennedy
这可能是一个非常糟糕的想法,具体取决于情况。如果他依赖引用计数系统来释放它们,并且其中一些被正确处理以使得泄漏的115个实例不是该列表中的所有实例,则将列表设置为拥有对象将导致双重释放错误。 - Mason Wheeler
那么,您要添加到TObjectList中的对象是什么?我希望不是GetAdapterNav返回的ICwcCDSAdapterNav接口引用。那些不是对象。那些是接口。下面有一个对象,但一旦通过接口引用它,您就会失去它。如果您想要一个接口列表,请使用TInterfaceList而不是TObjectList。如果您一直将接口保留在TObjectList中,则必须进行类型转换才能将其取回。这肯定会导致引用计数保持高于应该的水平。 - Rob Kennedy
显示剩余3条评论

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