GC memory leak

BlitzMax Forums/BlitzMax Programming/GC memory leak

JoshK(Posted 2006) [#1]
Calling the empty method whatever() causes a memory leak:

Type thing
	Global list:TList=New TList

	Method New()
		list.addfirst Self
	EndMethod
	
	Method whatever()
	EndMethod
	
EndType

GCCollect()
b=GCMemAlloced()
a:thing=New thing
'uncomment this and the mem leak occurs' a.whatever()
thing.list.remove a
a=Null
GCCollect()
Notify b+", "+GCMemAlloced()

End



Raz(Posted 2006) [#2]
bah, i hope this isn't the case for types where the sub type has a non-empty method of the same name


Dreamora(Posted 2006) [#3]
The problem is more or less the TList
While it has a clear in its destructor method, this one does never seem to be called.

If you use TLists, make sure that you manually clear them before you "null-ify" them at the current state.


degac(Posted 2006) [#4]
@Dreamora: please can you explain with an example what do you mean?
I've a similar problem and I want to resolve it in the 'right' way
thanks


Grey Alien(Posted 2006) [#5]
so list.clear is not acceptable?


Dreamora(Posted 2006) [#6]
list.clear is acceptable or even needed I should say.

degac: The problem is that the TList default destructor (method delete() ) has clear in it. Normally this would mean that all content is cleared when the TList goes out of scope. But something in its structuring (the links are applied as cyclic group which is bad on a GC system without secure root link checking, which BMs GC seems to be) fails badly. So if you null-ify something with a linked list or a linked list itself, you have to manually clear the list.
As short example:

strict
type TContainer
  field list:TList = createlist()

  method destroy()
    list.clear()
    list = null
  end method

  method add(obj:Object)
    list.addlast(obj)
  end method
end type


gccollect()  ' Mem Clear to clear the initialisation garbage
local cont:TContainer = new TContainer
local a:int = GCMemAlloced()
cont.add("Test 1")
cont.add("Test 2")


'cont.destroy()    '<---- Uncomment this line and see what happens
cont = null
gccollect()
print "memory difference: " gcmemalloced() - a


I should note that doing GCMemAlloced stuff on such a low memory usage level is quite pointless normally. It only works on BMs currently as it isn't written speed optimized but more toward memory optimized. A normal GC won't free any memory from system RAM unless its needed as dealloc takes time that could be used for more important stuff.
So if you want to do "memory leak tests" you normally have to take care that you use several MBs at least and see what happens then, as GC behavior is "unpredictable" on no ram usage (Where 1 reference is having a weight in the output as the example above of me or leadwerks)


Tachyon(Posted 2006) [#7]
So, to clarify: if you use a lot of Lists, and you need to "reset" them between Map or Level loads, .clear() is the way you should be doing it?


TomToad(Posted 2006) [#8]
I'm not getting any memory leak with your example. Niether with a.whatever commented or uncommented. I ran the code in a loop to see if anything accumulates after a while, nothing even then.


Dreamora(Posted 2006) [#9]
For resetting them, clear is the way anyway instead of recreating a new one.

But you as well need to call clear if you either do list = null or with an existing list list = new TList / createlist() ie when the list is going to go out of scope and be garbage collected.


JoshK(Posted 2006) [#10]
Everything "Dreamora" said about this is gibberish. "List" is a global parameter, not a field. It doesn't get destroyed.

The difference is calling the method whatever() causes an increased memory usage after the object is freed. If the method is not called, memory usage stays the same.

I created this simple example after I isolated the issue in my own code. It is causing my program to increase 1 mb of memory usage per second.


Brendane(Posted 2006) [#11]
I don't see any memory leak with a.whatever() uncommented in that code either.


DStastny(Posted 2006) [#12]
Run in release vs Debug


tonyg(Posted 2006) [#13]
Is it really a 'leak'?
If you run it in a loop it grabs that first bit of memory but doesn't continue to grab it...
Type thing
	Global list:TList=New TList

	Method New()
		list.addfirst Self
	EndMethod
	
	Method whatever()
	EndMethod
	
EndType
For x = 0 To 10
	GCCollect()
	b=GCMemAlloced()
	a:thing=New thing
	'uncomment this and the mem leak occurs' 
	a.whatever()
	thing.list.remove a
	a=Null
	GCCollect()
	Notify b+", "+GCMemAlloced()
Next
End



Brendane(Posted 2006) [#14]
Release version 'seems' to lose just a few bytes - it's constant too, no matter how many times the creation/destruction is called. The debug version shows no memory leak at all.


Dreamora(Posted 2006) [#15]
Leadwerks: I mentioned that this is with list in general and I as well mentioned that your "show off" as mine are quite pointless as a GC does not work memory critical when you have a memory usage that is that low that +- 1 reference makes a difference.

There is no leak in TList usage, just in TList collecting if you haven't manually cleared it.
(btw: leaks can't be shown by a 1 time operation but by something thats done through looping and raises at least constantly with the amount of steps which is not the case here)


tonyg(Posted 2006) [#16]
I still don't see a constant loss of bytes.
Debug : 15250 15250 for each loop.
Rel : 14434 14442 first loop then 14442 14442 constantly.


marksibly(Posted 2006) [#17]
Hi,

tonyg's da man...

*Always* look for mem leaks in loops! In fact, I think this may be in the docs...?

Looking for leaks in straight line code like this is silly - there are 1001 things that can keep something 'live' longer than you think it should be - debug/release mode, register allocation/spillage etc etc.

However, if you stick code in a loop and memory usage doesn't eventually 'settle', there's a problem.

And Dreamora, please note that the list is global! I don't really get any of the points you have made in this thread, but perhaps you've overlooked this fact. Whatever, your comments are very confusing!


JoshK(Posted 2006) [#18]
Thank you for the feedback. I was looking for the cause of a memory leak, and was attempting to reduce the problem.

I eventually identified a place where I had accidentally set an integer variable equal to a type. Changing the variable from an integer to the type fixed the leak.


Grey Alien(Posted 2006) [#19]
right, well glad that's cleared up then...


tonyg(Posted 2006) [#20]
Dreamora, and for anybody else who's worried, run that code in a loop...
Strict
Type TContainer
  Field list:TList = CreateList()

  Method destroy()
    list.clear()
    list = Null
  End Method

  Method add(obj:Object)
    list.addlast(obj)
  End Method
End Type

For Local x:Int = 0 To 10
	GCCollect()  ' Mem Clear to clear the initialisation garbage
	Local cont:TContainer = New TContainer
	Local a:Int = GCMemAlloced()
	cont.add("Test 1")
	cont.add("Test 2")
	
	
	'cont.destroy()    '<---- Uncomment this line and see what happens
	cont = Null
	GCCollect()
	Print "memory difference: " + (GCMemAlloced() - a)
	Print GCMemAlloced()
Next