.NET有一个名为的函数GC.KeepAlive(Object).其唯一目的是确保引用对象的生命周期持续到代码流到达调用.
除非有人与本机代码进行互操作,否则通常不需要这样做.
我有一种情况,我有一个通过JNI访问的C++对象的图形,其中某些根对象需要保持活动以保持孩子们活着.根对象和子对象都在JVM域中具有镜像.但是,如果在C++端(通过SWIG生成的终结器)收集和释放根对象,则子对象将变为无效,因为它们的C++后备对象将被释放.
这可以通过确保根对象图的局部变量的生命周期超过上次使用子对象来解决.所以我需要一个惯用的函数,它不会对对象做任何事情,但不会被优化掉或移动(例如从循环中提升).这就是GC.KeepAlive(Object).NET中的功能.
Java中的近似等价物是什么?
PS:一些可能的说明性代码:
class Parent {
long ptr;
void finalize() { free(ptr); }
Child getChild() { return new Child(expensive_operation(ptr)); }
}
class Child {
long ptr;
void doStuff() { do_stuff(ptr); }
}
// BAD CODE with potential for SIGSEGV
for (Parent p : getParents()) {
p.getChild().doStuff();
}
Run Code Online (Sandbox Code Playgroud)
麻烦的是,在doStuff执行时,GC释放Parent p将释放为Child分配的内存.已经观察到GC在实践中这样做.GC.KeepAlive可用的潜在修复:
// BAD CODE with potential for SIGSEGV
for (Parent p : getParents()) {
p.getChild().doStuff();
GC.KeepAlive(p);
}
Run Code Online (Sandbox Code Playgroud)
我可以例如调用toStringp,但我不会对其输出做任何事情.我可以暂时将p戳到一个数组中,但我怎么知道JVM不会丢弃商店?等等.
.net java java-native-interface garbage-collection finalizer
我已经在.NET中编程了四年(主要是C#)并且我广泛使用IDiposable,但我还没有找到终结者的需求.什么是终结者?
我正在使用一个搜索库,建议保持搜索句柄对象打开,这样可以使查询缓存受益.随着时间的推移,我发现缓存容易变得臃肿(几百兆并且不断增长),并且OOM开始启动.没有办法强制执行此缓存的限制,也没有计划它可以使用多少内存.所以我增加了Xmx限制,但这只是问题的临时解决方案.
最后,我想使这个对象所指的java.lang.ref.SoftReference.因此,如果系统在可用内存上运行不足,它将让对象运行并根据需要创建一个新对象.这会在新开始后降低一些速度,但这比击中OOM要好得多.
我看到的关于SoftReferences的唯一问题是没有干净的方式让他们的指示物最终确定.在我的情况下,在销毁搜索句柄之前我需要关闭它,否则系统可能会用完文件描述符.显然,我可以将这个句柄包装到另一个对象中,在其上写一个终结器(或挂钩到ReferenceQueue/PhantomReference)然后松开.但是,嘿,这个星球上的每篇文章都建议不要使用终结器,特别是 - 针对释放文件句柄的终结器(例如Effective Java ed.II,第27页).
所以我有些困惑.我应该小心地忽略所有这些建议并继续.否则,还有其他可行的替代方案吗?提前致谢.
编辑#1:根据Tom Hawtin的建议测试了一些代码后添加了下面的文字.对我来说,似乎任何一个建议都没有用,或者我错过了一些东西.这是代码:
class Bloat { // just a heap filler really
private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;
private final int ii;
public Bloat(final int ii) {
this.ii = ii;
}
}
// as recommended by Tom Hawtin
class MyReference<T> extends SoftReference<T> {
private final T hardRef;
MyReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.hardRef = referent;
}
}
//...meanwhile, somewhere in the neighbouring galaxy...
{
ReferenceQueue<Bloat> …Run Code Online (Sandbox Code Playgroud) 我了解到调用Object的wait()方法将释放对象监视器(如果存在).
但我有一些关于notify()通过另一个线程调用此对象的问题:
如果另一个(第3个)线程同时拥有对象监视器,那么等待线程是否会被唤醒?
如果第三个线程调用wait()此对象,那么等待线程会被唤醒吗?
是否可以确定线程是否在等待通知特定对象(java 1.4/java 5)
如果wait()在finalize()方法中调用会发生什么?
我遇到了一个行为不端的库,它在终结器中抛出异常,这当然会导致应用程序崩溃.
为了避免这种情况,我尝试在自己的AppDomain中加载库,但异常仍然会冒泡到表面并导致应用程序崩溃.
正如在MSDN上记录的AppDomain.UnhandledException那样,注册不会阻止异常冒泡,但我很惊讶没有其他方法可以在"子AppDomain"中捕获这样的异常.
插件主机或使用AppDomains的应用程序如何沙箱化可能有害的代码,如何阻止未处理的异常?它实际上可能吗?
注意:我已经有了另一种解决方法,就是这里描述的解决方法.错误的终结器位于一个长期存在的对象上,似乎只在关闭期间收集,因此足以隐藏用户的这种"虚假"错误.尽管如此,我发现这种解决方法很脆弱,因为它会隐藏其他真正的错误,或者如果先前收集了对象,则可能会炸毁我的应用程序.
我的代码中有这些行:
// create tab4
intent = new Intent(this, ActWhereAmI.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
tabspecWhereAmI = tabHost
.newTabSpec("tab4")
.setIndicator(Utilities.prepareTabView(this,"where am I"))
.setContent(intent);
tabHost.addTab(tabspecWhereAmI);
public static View prepareTabView(Context context, String text) {
View view = LayoutInflater.from(context).inflate(
R.layout.tab_indicator, null);
TextView tv = (TextView) view.findViewById(R.id.tabIndicatorTextView);
tv.setText(text);
return view;
}
Run Code Online (Sandbox Code Playgroud)
当应用程序运行该行时,tabHost.addTab(tabspecWhereAmI);我只在LogCat中得到以下错误,程序运行没有任何问题:
10-17 13:38:01.164: W/MapActivity(4815): Recycling dispatcher android_maps_conflict_avoidance.com.google.googlenav.datarequest.DataRequestDispatcher@413c8658
10-17 13:38:01.171: V/MapActivity(4815): Recycling map object.
10-17 13:38:01.335: W/MapActivity(4815): Recycling dispatcher android_maps_conflict_avoidance.com.google.googlenav.datarequest.DataRequestDispatcher@413c8658
10-17 13:38:01.335: V/MapActivity(4815): Recycling map object.
10-17 13:38:01.554: D/dalvikvm(4815): GC_CONCURRENT freed 776K, 23% free 10286K/13255K, paused 2ms+7ms …Run Code Online (Sandbox Code Playgroud) 我有这段代码
public class Publisher
{
public event EventHandler SomeEvent;
}
public class Subscriber
{
public static int Count;
public Subscriber(Publisher publisher)
{
publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
}
~Subscriber()
{
Subscriber.Count++;
}
private void publisher_SomeEvent(object sender, EventArgs e)
{
// TODO
}
}
Run Code Online (Sandbox Code Playgroud)
在我的应用程序的主要方法中我有
static void Main(string[] args)
{
Publisher publisher = new Publisher();
for (int i = 0; i < 10; i++)
{
Subscriber subscriber = new Subscriber(publisher);
subscriber = null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Subscriber.Count.ToString());
}
Run Code Online (Sandbox Code Playgroud)
如果我运行它,我将输出0.如果我从代码中删除事件订阅,我将得到期望的结果 - 这是10.
当 …
编辑 有关其他详细信息,请参阅问题底部的编辑注释.
原始问题
我有一个CacheWrapper类,它在MemoryCache内部创建并保存在.NET 类的实例上.
MemoryCache将自己挂钩到AppDomain事件中,因此除非明确处理,否则它永远不会被垃圾收集.您可以使用以下代码进行验证:
Func<bool, WeakReference> create = disposed => {
var cache = new MemoryCache("my cache");
if (disposed) { cache.Dispose(); }
return new WeakReference(cache);
};
// with false, we loop forever. With true, we exit
var weakCache = create(false);
while (weakCache.IsAlive)
{
"Still waiting...".Dump();
Thread.Sleep(1000);
GC.Collect();
GC.WaitForPendingFinalizers();
}
"Cleaned up!".Dump();
Run Code Online (Sandbox Code Playgroud)
由于这种行为,我认为我的MemoryCache实例应该被视为非托管资源.换句话说,我应该确保它被放置在CacheWrapper的终结器中(CacheWrapper本身是Disposable遵循标准的Dispose(bool)模式).
但是,当我的代码作为ASP.NET应用程序的一部分运行时,我发现这会导致问题.卸载应用程序域时,终结器将在我的CacheWrapper类上运行.这反过来试图处理该MemoryCache实例.这是我遇到问题的地方.似乎Dispose试图从IIS加载一些配置信息失败(大概是因为我正在卸载app域,但我不确定.这是我的堆栈转储:
MANAGED_STACK:
SP IP Function
000000298835E6D0 0000000000000001 System_Web!System.Web.Hosting.UnsafeIISMethods.MgdGetSiteNameFromId(IntPtr, UInt32, IntPtr ByRef, Int32 ByRef)+0x2
000000298835E7B0 000007F7C56C7F2F System_Web!System.Web.Configuration.ProcessHostConfigUtils.GetSiteNameFromId(UInt32)+0x7f
000000298835E810 000007F7C56DCB68 System_Web!System.Web.Configuration.ProcessHostMapPath.MapPathCaching(System.String, …Run Code Online (Sandbox Code Playgroud) 离开活动后永远不会调用终结器.这是否意味着即使我继续进行下一个活动,活动仍然存在.
namespace XamarinTest {
[Activity(Label = "XamarinTest", Icon = "@drawable/icon")]
public class MainActivity : Activity {
private int count = 1;
private TextView density;
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.ScreenData);
density = FindViewById<TextView>(Resource.Id.Density);
var pendingInent = new Intent();
pendingInent.SetFlags(ActivityFlags.ClearTop);
pendingInent.SetClass(this, typeof(TestActivity));
StartActivity(pendingInent);
Finish();
}
~MainActivity() {
Console.WriteLine("Finalizer called");
}
protected override void Dispose(bool disposing){
if (disposing) {
density.Dispose();
density = null;
}
base.Dispose(disposing);
}
}
}
Run Code Online (Sandbox Code Playgroud) 我理解在.NET中,即使对象是部分构造的(例如,如果从构造函数中抛出异常),终结器也会运行,但是什么时候构造函数从未运行过呢?
背景
我有一些C++/CLI代码可以有效地执行以下操作(我不相信这是特定于C++/CLI,但这是我已经准备好的情况):
try {
ClassA ^objA = FunctionThatReturnsAClassA();
ClassB ^objB = gcnew ClassB(objA); // ClassB is written in C# in a referenced project
...
}
catch (...) {...}
Run Code Online (Sandbox Code Playgroud)
我有一个100%可重复的情况,如果一个异常被抛出FunctionThatReturnsAClassA(),然后一个GC被触发(似乎通过再次运行这个代码可靠地触发,但等待一段时间也有效),ClassB的终结器被调用.
现在,通过跟踪输出,我可以确认ClassB的构造函数没有运行(这当然是你所期望的).所以不知何故,objB显然被分配并添加到终结器列表中,之后甚至满足调用其构造函数的前提条件(即从FunctionThatReturnsAClassA()收集结果).
这只发生在调试器外部运行的优化版本中.我可以进行各种小的更改,导致终结器没有运行 - 例如在两个语句之间插入另一个方法调用,或者(据说,我认为)将"gcnew ClassB"移动到一个单独的函数中,该函数返回宾语.
在我看来,gcnew语句的分配部分在某种程度上被重新排序并在前一个语句之前运行,但是这个重新排序没有反映在生成的MSIL代码中(打败了我最初的假设,这只是另一个C++/CLI代码gen bug ).此外,将"错误"状态与任何"固定"状态之间生成的MSIL代码进行比较,显示没有意外的结构变化.
我已经在调试器中查看了生成的x86代码,到目前为止它看起来并不奇怪,但我没有深入分析它,无论如何我无法在调试器中重现这种行为所以我不是100%确保我从调试器获得的代码与显示奇怪行为的代码相同.
所以它可能是一个MSIL-> x86代码的基本怪癖,或者它可能是一个处理器指令重新排序(前者似乎更有可能但我没有通过在行为发生时更加努力地获取内存中的确切代码来证实 - 这是我的下一步).
题
因此,在.NET中对象的分配是否与该对象的构造函数调用分开并重新排序是有效的(因为缺少一个更好的术语)?