Garbage Collection misundertandings

BlitzMax Forums/BlitzMax Beginners Area/Garbage Collection misundertandings

Matthew25(Posted 2013) [#1]
Hi,

I'm working on my first program and given that i'm not used to working with a garbage collector looking over my shoulder, I decided to see if I had any memory leaks. Part of this was counting how many times an object was constructed and destroyed and I came across this problem that I am curious about.
I have a menu function that returns an object that contains various objects to use in the game, yet when I pass these objects owned by the menu result object to other functions, the menu result object never gets destroyed even after setting it to null. Here is the simplest example code as a runnable program:

Type Interior
End Type

Type Exterior
	Field i:Interior

	Method New()
		Print "Constructor"
		i = New Interior
	End Method
	
	Method Delete()
		Print "Destructor"
	End Method
	
	Method GetInterior:Interior()
		Return i
	End Method
End Type



Function InteriorParameter(p:Interior)
End Function

Function CreateExterior:Exterior()
	Local e:Exterior = New Exterior
	Return e
End Function

Local f:Exterior = CreateExterior()
InteriorParameter(f.GetInterior())
f = Null

While(1)
	GCCollect()
	PollSystem()
Wend



CreateExterior() creates an exterior object that contains an interior object. The interior object is then passed to InteriorParameter() function, when done the reference to the exterior object is made null.

Even with the infinite loop at the end to stop the program ending before the garbage collector does its business, the f never seems to be destroyed. Take out the InteriorParameter call and everything works as expected.

Actually when typing this I discovered, it gets stranger, if I add the following function outside of any class

Function ExteriorParameter(p:Exterior)
	InteriorParameter(p.GetInterior())
End Function


And I change the change the InteriorParameter(f.GetInterior()) line to ExteriorParameter(f) , everything get created and destroyed as you would expect.

What is causing this?

Thanks,

Matt


Floyd(Posted 2013) [#2]
On the first try there was no output. The program never ends so I had to terminate it.

I'm not sure how PollSystem works and thought maybe the OS needs more time. So I added a "Delay 100" to the final loop. Now "Destructor" did get printed. Maybe the object was being destroyed but for some reason the output wasn't shown.

After that I took out the Delay, so the code was again in its original form. This time "Destructor" was displayed. And that's with the same code which did not produce any output the first time.

So to summarize, I have no idea what is happening. But the endless loop with nothing but GCCollect and PollSystem feels wrong.


Jesse(Posted 2013) [#3]
if you are trying to find out if the garbage collector works correctly, it does. you don't need to bother with the specifics just know that the garbage collector does it's thing in the background and to the unknowing eye it's called at random. the command "GCMemAlloced()" lets you see how much memory is being consumed by the application. so you can display it during program execution to see how much your program is using memory. if you see your program increase in megabytes and never release then you have a problem otherwise there is no problem and just let it do its' job it was made to do. :)


Yasha(Posted 2013) [#4]
I'm not too hot on assembly myself, but after building you can look in the .bmx folder to view the .s files created of your program.

In this case, it looks like the compiler is able to determine statically that garbage collection is completely unnecessary and just not bothering to engage it for the object at f. The fact that one of these situations still leaves it available for GCCollect is probably caused by a hidden factor (presumably something to do with total code size).

Two of the general guidelines about GC in any language are that finalisers are 1) allowed to run when the runtime decides they should, and 2) therefore may never run at all. The fact that it doesn't run immediately on the last reference being nulled is not in itself a bug, it could well be an optimisation; either removing a call it reckons is unnecessary, or pooling a bunch of small dead objects to free in one go later on.

You might be tempted to argue that this means the program has improper behaviour, because your destructor has an observable side effect which might never appear or might run at the wrong time - it doesn't; rather, this is a demonstration of why important stuff (certainly anything with observable effects) isn't supposed to go in finalisers.


Henri(Posted 2013) [#5]
I'm not sure but at first glance your example seems to create a small memory leak if you don't set i:Interior field to null before setting f:Exterior to null. As I understand Delete method it's highly unstable place to but any commands, but it seems indeed that object is collected even without pintout.


-Henri


col(Posted 2013) [#6]

I'm not sure but at first glance your example seems to create a small memory leak if you don't set i:Interior field to null before setting f:Exterior to null.


The GC will pick that up and clean up properly.

The max compiler is quite intelligent at picking up pointlessly declared variables ie variables that are not actually used with anything else. That being said your code does use the variable with passing it into a function and so it will be created properly.

As Jesse already said, the inner workings of the GC do make it seem like it doesnt work properly especially with small examples like this. Rest assured it works even if it does leave the first reference in place for its own reasons that probably only Mark knows why.

This example shows it working much more clearly:



Matthew25(Posted 2013) [#7]
OK thanks everyone for the help. I assumed on calling GCCollect() it would destroy everything it possibly could, but it makes sense that there would be some optimisation behind the scenes that means it doesn't do this.

Cols example works, so I'll take a leap of faith in the GC and keep an eye on what this mentioned GCMemAlloced() function says over time.....

Thanks.