我最近与同事讨论了Dispose实施的价值和类型IDisposable.
我认为即使没有非托管资源可以清理IDisposable,也应该尽快清理类型.
我的同事有不同的想法; 执行IDisposable,如果你没有任何非托管资源为你的类型,最终会被垃圾收集是没有必要的.
我的论点是,如果你有一个想要尽快关闭的ADO.NET连接,那么实现IDisposable并且using new MyThingWithAConnection()有意义.我的同事回答说,在封面下,ADO.NET连接是一种非托管资源.我对他回复的答复是,最终所有东西都是一种非托管资源.
我知道推荐的一次性模式,如果Dispose被调用,你可以免费使用托管和非托管资源,但是如果通过终结器/析构函数调用,则只有免费的非托管资源(前面有关于如何提醒消费者不正确使用您的IDisposable类型的博客)
所以,我的问题是,如果你有一个不包含非托管资源的类型,是否值得实现IDisposable?
显然,Constrained Execution Region保证不适用于迭代器(可能是因为它们的实现方式和所有),但这是一个bug还是设计?[见下面的例子.]
关于CER与迭代器一起使用的规则是什么?
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
class Program
{
static bool cerWorked;
static void Main(string[] args)
{
try
{
cerWorked = true;
foreach (var v in Iterate()) { }
}
catch { System.Console.WriteLine(cerWorked); }
System.Console.ReadKey();
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
unsafe static void StackOverflow()
{
Big big;
big.Bytes[int.MaxValue - 1] = 1;
}
static System.Collections.Generic.IEnumerable<int> Iterate()
{
RuntimeHelpers.PrepareConstrainedRegions();
try { cerWorked = false; yield return 5; }
finally { StackOverflow(); }
}
unsafe struct Big { public fixed byte Bytes[int.MaxValue]; …Run Code Online (Sandbox Code Playgroud) 在我的申请中,我有以下记录:
TTransaction = record
Alias: string
Description: string
Creation: TDateTime
Count: Integer
end;
Run Code Online (Sandbox Code Playgroud)
我在这个数组中使用此记录:
Transactions = array of TTransaction;
Run Code Online (Sandbox Code Playgroud)
我在运行时保持数组加载,但在给定时间我需要清除所有数据并添加一些新数据.
仅仅使用它就足够了:
SetLength(Transactions, 0); ?
Run Code Online (Sandbox Code Playgroud)
或者我需要完成一些事情吗?
在Java中,覆盖该finalize方法会得到一个糟糕的说唱,虽然我不明白为什么.类似于FileInputStream使用它来确保close在Java 8和Java 10中调用.然而,Java 9引入java.lang.ref.Cleaner了使用PhantomReference机制而不是GC终结.起初,我认为这只是向第三方类添加finalization的一种方式.但是,其javadoc中给出的示例显示了一个可以使用终结器轻松重写的用例.
我应该finalize用Cleaner 重写我的所有方法吗?(当然,我没有很多.只是一些使用OS资源的类,特别是对于CUDA互操作.)
据我所知,Cleaner(通过PhantomReference)避免了一些危险finalizer.特别是,您无法访问已清理的对象,因此您无法复活它或其任何字段.
然而,这是我能看到的唯一优势.清洁工也是非平凡的.事实上,它和终结都使用了ReferenceQueue!(难道你不喜欢阅读JDK是多么容易吗?)它比完成更快吗?它是否避免等待两个GC?如果许多对象排队等待清理,它会避免堆耗尽吗?(所有这些的答案在我看来都不是.)
最后,实际上没有任何保证阻止您在清理操作中引用目标对象.小心阅读长API注意!如果你最终引用了这个对象,那么整个机制将会默默地中断,而不像终结总是试图跛行.最后,虽然终结线程由JVM管理,但创建和保存Cleaner线程是您自己的责任.
C#语言规范3.0的第10.13节"析构函数"声明如下:
析构函数不是继承的.因此,除了可以在该类中声明的类之外,类没有析构函数.
" C#编程指南"的 "Destructors"部分包含一个示例,演示如何调用继承层次结构中的析构函数,包括以下语句:
... ...类的析构函数是自动调用的,按顺序,从最大派生到最小派生.
我用各种实际例子对此进行了调查,其中包括一个定义析构函数的基类,一个派生类,它继承自基类,并没有定义析构函数.创建派生类的实例,使该实例的全部引用走出去的范围,然后迫使垃圾收集表明,当派生类的实例被敲定在基类中定义的析构函数被调用.
我的问题是"析构函数不是继承的"实际意味着什么,因为虽然你不能显式地调用析构函数,但是继承链中的析构函数会被自动调用,并且即使派生类没有定义析构函数,也会调用基类析构函数?
是否与垃圾收集器而不是C#语言/编译器实现的最终化有些微妙的语义区别?
编辑1:
虽然C#语言规范还声明"实例构造函数不是继承的",但与构造函数相关的行为与析构函数明显不同,并且更符合IMO与"未继承"的术语,如下例所示:
public class ConstructorTestBase
{
public ConstructorTestBase(string exampleParam)
{
}
}
public class ConstructorTest: ConstructorTestBase
{
public ConstructorTest(int testParam)
: base(string.Empty)
{
}
}
...
// The following is valid since there is a derived class constructor defined that
// accepts an integer parmameter.
ConstructorTest test1 = new ConstructorTest(5);
// The following is not valid since the base class constructor is not inherited
// by the …Run Code Online (Sandbox Code Playgroud) 我对最终定稿的理解是这样的:
为了清理或回收对象占用的内存,垃圾收集器开始运行.(自动调用?)
垃圾收集器然后取消引用该对象.有时,垃圾收集器无法访问该对象.然后调用finalize进行最后的清理处理,之后可以调用垃圾收集器.
这是对最终确定的准确描述吗?
Java中可终结对象的讨论通常讨论当最终化对象(及其相关资源)无法快速被垃圾收集时发生的常见间接成本.
目前,我更感兴趣的是,无论是在内存方面还是在对象分配时间内,可实现的最终直接成本是什么.我已经看到在很多地方倾向于提到这种成本的存在,例如,Oracle关于最终化内存保留问题的文章指出:
当
obj被分配时,JVM内部记录中obj是终结.这通常会减慢现代JVM具有的快速分配路径.
JVM如何记录对象实例的最终结果,以及这样做的内存和性能成本是多少?
对于对我的具体应用感兴趣的人:
我们生产和保留了数百万个极其轻巧的物体; 添加一个指向这些对象的指针是非常昂贵的,所以我们已经做了相当多的工作来删除它们的指针,而是使用较小的数字ID打包到字段的一部分位.解包该数字允许从使用Map存储它们的池中检索具有该id的共享不可变属性.
剩下的问题是如何处理不再使用的属性值的垃圾收集.
已经考虑的一种策略是使用引用计数; 当创建对象并检索值的池化id时,该值的引用计数递增; 当它不再使用时,必须递减.
确保此减量发生的一个选项是添加以下finalize方法:
public void finalize() {
Pool.release(getPropertyId());
}
Run Code Online (Sandbox Code Playgroud)
但是,如果可最终化的行为意味着必须保留指向该对象的附加指针,那么对于该应用程序而言,可最终确定的前期成本将被认为是高的.如果它意味着必须分配额外的对象,那几乎肯定会过高......因此,我的问题是:最终化的直接前期成本是多少?
有没有办法获得使用runtime.SetFinalizer和尚未运行的终结器总数?
我们正在考虑struct在我们的某些产品中添加一个注册的终结器以释放使用的内存malloc,并且该对象可能具有相对较高的分配率.如果我们能够监控终结器的数量,确保它们不会堆积并触发内存不足错误(就像它们倾向于与其他垃圾收集器一样),那将是很好的.
(我知道显式释放会避免这个问题,但我们不能改变现有的代码,它不会调用Close函数或类似的东西.)
我试图找到一种安全/确定的方法来释放封装在OleVariant中的接口.
AFAICS Delphi在程序结束时发布接口引用,但在我的情况下我必须提前完成,因为我必须关闭COM.
procedure Test;
var
LLibrary: OleVariant;
begin
CoInitialize(nil);
try
LLibrary := Null;
try
LLibrary := CreateOleObject(LibraryName);
finally
LLibrary := Unassigned; // <-- I would like to release the interface here
end;
finally
CoUninitialize; // <-- Shutdown of COM
end;
end; // <-- The compiler releases the interface here
Run Code Online (Sandbox Code Playgroud)
我把OleVariant放在一个额外的类实例中,我可以在调用之前释放它CoUninitialize.
procedure Test;
var
Container: TLibraryContainer; // Holds the OleVariant
begin
CoInitialize(nil);
try
Container := TLibraryContainer.Create;
try
{...}
finally
Container.Free;
end;
finally
CoUninitialize;
end;
end;
Run Code Online (Sandbox Code Playgroud)
这个解决方案是安全的还是我忽略了一个更好的解决方案?
我需要在内存使用方面优化我的应用程序。所以我使用了 .net 性能分析器...但是我的应用程序中的一些引用仍然存在并且即使我强制它收集也永远不会被 GC 收集。
活着的引用是“终结句柄”类型。我不知道该怎么做才能删除这种引用......请帮忙。
finalization ×10
c# ×4
java ×3
.net ×2
delphi ×2
finalizer ×2
idisposable ×2
cer ×1
com ×1
delphi-2007 ×1
delphi-6 ×1
destructor ×1
finalize ×1
go ×1
java-9 ×1
jvm ×1
record ×1
reference ×1