WeakReference not working?

P

Pieter

Hi,

We have written a cache, which can have different behaviours. One of these
is a WeakReference-Cache. The purpose is that, once an object isn't referred
anymore in the application, it should dissappear from the cache.

This works almost always fine, but not for some objects, and I can't find
out why. For some reason, even when I do a "MyObject = Nothing" and a
"GC.Collect", it still has its "WeakReference.IsAlive = True"...

Anybody knows why this happens? And what can I do about it? Should I
implement IDisposable to all my objetcs? And how does that work? Do I have
to do anything special in the overriden Dispose-method?


Thanks a lot in advance,


Pieter



This is how we did the GC.Collect and the removal of the
not-anymore-used-objects...

Private Shared Sub GarbageCollect()
Dim lstID As New List(Of Integer)

GC.Collect() 'premier collect
GC.WaitForPendingFinalizers() 'attend la fin du thread
GC.Collect() 'dexième collect pour tout enlever
GC.WaitForPendingFinalizers() 'attend la fin du thread

For Each i As Integer In dicCacheWeakReference.Keys
If Not dicCacheWeakReference(i).IsAlive Then
lstID.Add(i) 'si l'objet n'existe plus, l'ajoute dans la
liste à effacer
End If
Next

For Each intID As Integer In lstID
dicCacheWeakReference.Remove(intID) 'efface les reférences morte
du cache
Next
End Sub
 
?

=?ISO-8859-1?Q?G=F6ran_Andersson?=

Pieter said:
Hi,

We have written a cache, which can have different behaviours. One of these
is a WeakReference-Cache. The purpose is that, once an object isn't referred
anymore in the application, it should dissappear from the cache.

This works almost always fine, but not for some objects, and I can't find
out why. For some reason, even when I do a "MyObject = Nothing" and a
"GC.Collect", it still has its "WeakReference.IsAlive = True"...

Anybody knows why this happens?

A garbage collection isn't guaranteed to collect _all_ objects that are
collectable, it only collects the objects that are efficient to collect.

A garbage collection usually only collects objects in the first
generation heap. As the objects that you cache probably lives for a
longer time than most objects, they are likely to have been moved to the
second or third generation heap.
And what can I do about it? Should I
implement IDisposable to all my objetcs? And how does that work? Do I have
to do anything special in the overriden Dispose-method?

Why is there any reason to do anything about it? If the garbage
collector doesn't collect the objects, the memory that they are using
isn't needed. If the application runs out of memory in the current heap,
the garbage collector does a more thorough collection to collect
anything that is possible, before more memory is requested from the system.
Thanks a lot in advance,


Pieter



This is how we did the GC.Collect and the removal of the
not-anymore-used-objects...

Private Shared Sub GarbageCollect()
Dim lstID As New List(Of Integer)

GC.Collect() 'premier collect
GC.WaitForPendingFinalizers() 'attend la fin du thread
GC.Collect() 'dexième collect pour tout enlever
GC.WaitForPendingFinalizers() 'attend la fin du thread

For Each i As Integer In dicCacheWeakReference.Keys
If Not dicCacheWeakReference(i).IsAlive Then
lstID.Add(i) 'si l'objet n'existe plus, l'ajoute dans la
liste à effacer
End If
Next

For Each intID As Integer In lstID
dicCacheWeakReference.Remove(intID) 'efface les reférences morte
du cache
Next
End Sub

Why are you calling GC.Collect at all? The memory management handles
itself, and there is hardly ever any reason to interfer with it.

Unless you know something important about the memory usage that the
garbage collector don't, you will likely reduce the performance of the
application rather than improve it.
 
P

Pieter

Göran Andersson said:
Why is there any reason to do anything about it?

The whole purpose is not collecting memory, but throwing away the object...

This is what is needed: If the user wants to open object A, but object A is
already opened (and used), it should return the same objet. But if it's not
opened (or opened before but not used anymore), it should return a fresh
copy of the database.

That's exactly why we are using WeakReferences...

If the application runs out of memory in the current heap, the garbage
collector does a more thorough collection to collect anything that is
possible, before more memory is requested from the system.

And can I force him to do this more thorough collection?


And better (because I don't really like using the garbage collector): isn't
there another way to achieve this behaviour?

Thanks,

Pieter
 
H

Henning Krause [MVP - Exchange]

Pieter said:
The whole purpose is not collecting memory, but throwing away the
object...

This is what is needed: If the user wants to open object A, but object A
is already opened (and used), it should return the same objet. But if it's
not opened (or opened before but not used anymore), it should return a
fresh copy of the database.

That's exactly why we are using WeakReferences...

And that's how it will work. But you can't force the GC to free your
instance. It will do if it deems it necessary. Just don't worry about it.
And can I force him to do this more thorough collection?
You can, but you shouldn't. Call
GC.Collect(2);
GC.WaitForPendingFinalizers();
GC.Collect(2);

But don't do it. In most cases, this will make things worse.
And better (because I don't really like using the garbage collector):
isn't there another way to achieve this behaviour?

No... that's (thankfully) .NET.

Kind regars,
Henning Krause
 
J

John

Hi Pieter,

The normal purpose for creating weakly referenced objects is to
allow them to remain in memory as long as memory permits.
If there is memory pressure the garbage collector will run and
wr objects may be collected. This way if memory is abundant
your wr cache objects will be in memory, if not, they will be
collected (forcing you to re-load them the next time they are
needed).

You can simulate this situation by running GC.Collect() and
then WaitForPendingFinalizers(). If you are using wr correctly
they will be be removed from memory after GC.Collect.

Please note that I am *not* saying that you run the garbage
collector manually when using wr. The above is for testing/debugging
purposes only to help simulate the situation where wr objects
are removed from memory.

One mistake people make is creating a regular (non-weak)
object reference to an object that is the target of a wr. In
this case the object will *survive* garbage collections which is
not the behavior you wanted when you decided on using weak
references.

You can use CLR Profiler (free) or .NET Memory Profiler ($)
to help you track down the reference that is causing the wr
to not be collected.

John
 
J

John

The fact that a weak reference is still alive after *multiple*
full garbage collections is an indication that there is regular
object reference pointing to the object. This is a memory
leak and should be corrected.

I agree with you that in most cases GC.Collect should
not be used except for debugging. However, if an object (that
should be collectable) survives multiple full collections then it is
in fact not collectable and is evidence of a memory leak.

John
 
J

John

If done properly a weak reference will be collected
during a full garbage collection (GC.Collect()). The
fact that it is not collected shows that Pieter has a memory
leak that needs to be corrected.

John
 
H

Henning Krause [MVP - Exchange]

Hi John,
You are right.

There is a pretty cool memory analzyer from SciTech. They also have a 30
days trial version.

Kind regards,
Henning Krause
 
?

=?ISO-8859-1?Q?G=F6ran_Andersson?=

Pieter said:
The whole purpose is not collecting memory, but throwing away the object...

This is what is needed: If the user wants to open object A, but object A is
already opened (and used), it should return the same objet. But if it's not
opened (or opened before but not used anymore), it should return a fresh
copy of the database.

That's exactly why we are using WeakReferences...

A WeakReference doesn't work that way. The memory management in .NET
doesn't use reference counting, so there is no way that a WeakReference
can know when there are no more references to the object.

The way to control the life cycle of an object in .NET is to use the
IDisposable interface.

What you need to do is use reference counting in your cache, and
implement the IDisposable interface in the object. When the object is
disposed, it should inform the cache so that it can decrement the
reference count, so that the object can be removed from the cache when
the reference count reaches zero.
And can I force him to do this more thorough collection?

Yes, but you shouldn't.
And better (because I don't really like using the garbage collector): isn't
there another way to achieve this behaviour?

Exactly. You are trying to use the garbage collector for something that
it can't do, so you should implement it in a way that works instead. :)
 
H

Henning Krause [MVP - Exchange]

Hello,
A WeakReference doesn't work that way. The memory management in .NET
doesn't use reference counting, so there is no way that a WeakReference
can know when there are no more references to the object.

Yes it does work this way. A WeakReference does not have a strong reference
to the object but holds the GCHandle of the item.
The way to control the life cycle of an object in .NET is to use the
IDisposable interface.

Nope. IDisposable is used to release unamanged resources. Not managed ones.
What you need to do is use reference counting in your cache, and implement
the IDisposable interface in the object. When the object is disposed, it
should inform the cache so that it can decrement the reference count, so
that the object can be removed from the cache when the reference count
reaches zero.

The whole GC is about not having to worry about reference counting. Why do
you want to bring it back?

A WeakReference will work fine in a cache. The GC will release it if there
is no other strong reference.

Kind regards,
Henning Krause
 
P

Pieter

Thanks all for the help. I thought too about a memory leak, but it's jsut
that weard because:
- We have like 7 'main' objects that are threated exactly the same, but only
2 of them have this problem.
- All are of the same kind, with some parent-child lists in it, but nothing
special
- The cache and weak reference behaviour is tested with NUnit: An objectsi
created, set to Nothing, GC.collected, and that's it. No other references
pointing to it...

I'll take a look at the profilers and let something know if I find something
:)

Thanks a lot,

Pieter


For instance the tests that fails:
<Test()> Public Sub CacheTest()

'creation of a new clsCotation, based on 2 other objects
Dim clsF As New clsCotation(MyRequisition, MyFournisseur)

clsF.Titre = "Cache Test"
clsF.PrixTotalNet = 20 'doit avoir prix pour PF
'save the object clsF
facCotation.SaveOne(clsF)
'save the ID
intCotationID = clsF.CotationID

'set clsF = nothing (and all it's child-list and child-objects etc.)
clsF.MyArticles = Nothing
clsF.MyConditionnements = Nothing
clsF.Requisition = Nothing
clsF.Fournisseur = Nothing
clsF.Client = Nothing
clsF = Nothing

'another clsCotation
Dim clsF3 As clsCotation
'select the cotation from the cache, but it should be GC-ed... (but
isn't!!!)
clsF3 = facCotation.SelectOneByID(intCotationID,
CacheUsage.cuCacheOnly)
Assert.AreEqual(Nothing, clsF3, "because the object should have been
GC-ed, it should return Nothing, but this tests fails!")
clsF3 = Nothing



John said:
The fact that a weak reference is still alive after *multiple*
full garbage collections is an indication that there is regular
object reference pointing to the object. This is a memory
leak and should be corrected.

I agree with you that in most cases GC.Collect should
not be used except for debugging. However, if an object (that
should be collectable) survives multiple full collections then it is
in fact not collectable and is evidence of a memory leak.

John
 
P

Pieter

John said:
You can use CLR Profiler (free) or .NET Memory Profiler ($)
to help you track down the reference that is causing the wr
to not be collected.

Ouwch, I got very nice and colourfull schema's with the CLR Profiler, but in
which screen exactly can I track down my problem? hihi :)
 
?

=?ISO-8859-1?Q?G=F6ran_Andersson?=

Henning said:
Yes it does work this way. A WeakReference does not have a strong
reference to the object but holds the GCHandle of the item.

That's not what we are talking about. What Peter wants is to know if the
object is used, not if it has been collected. A weak reference can not
be used for that, because it doesn't work that way.
Nope. IDisposable is used to release unamanged resources. Not managed ones.

As there are no destructors in .NET, the IDisposable interface is used
when you need to control the life cycle of objects. It's ususally used
because the object has unmanaged resourctes that needs to be released,
but there is nothing that requires the object to have unmanaged resources.
The whole GC is about not having to worry about reference counting. Why
do you want to bring it back?

Because Peter needs to know if the object is used or not, and as there
can be more than one reference to the object, the references needs to be
counted.
A WeakReference will work fine in a cache. The GC will release it if
there is no other strong reference.

Yes, but that is not what Peter wants. Or in his own words:

"The whole purpose is not collecting memory, but throwing away the object"
 
P

Pieter

I'm not finding a way via those Profilers to see what's putting a reference
to my object :-S Anybody has another idea? Or can someone point me out how I
should be able to see it?

Thanks a lot,


Pieter
 
Top