Object not being deleted - Why?
BlitzMax Forums/BlitzMax Programming/Object not being deleted - Why?
| ||
This question is a bit vague I suppose, but I can't really post all the involved source, so vague is as good as it's going to get. My game objects have a Destroy() method which cleans up after them. I think I do this correctly, but it's always possible I've made a mistake, so I write a little debug note in there to help me spot any circular references. At the end of my Destroy() method I have this code : GCCollect() ' MAKE SURE OBJECTS WE DESTROYED ARE THEMSELVES COLLECTED AND LOSE THEIR REFS DebugLog Byte Ptr(Self)[- 4] + " REFERENCES REMAIN" If this is ever greater than one, there is a circular reference I haven't broken. If it's one ( that reference being the one that called this destroy() method in the first place ) then it's clean as a whistle. My object clears this hurdle, passing back a 1. This sole reference is a local variable, used to cycle through all the objects in a TMap. I've even confirmed 100% for certain that this TMap does not contain the object. As soon as this local variable goes out of scope, the object *must* be ready for collection, right? Wrong. It never gets collected. I call GCCollect in my main loop ( only for ease of debugging purposes, of course ) and the delete method is never called. This makes no sense to me. I'm confirming the 1 reference, I know what that reference is, and I know that 1 reference can't last, but it's still not being deleted. The only possible ( logical ) conclusion is that I create another reference after calling the destroy method and before the local variable goes out of scope. The only problem with that theory is that the loop through all the objects in the TMap does nothing else. It simply calls an update method and the call to destroy ( if executed ) is right at the end of the destroy method. Nothing else is executed. What possible thing have I missed? I can see no logical reason for the object not being collected. Is there a flaw in my logic anywhere? |
| ||
I thought it was adviced against running a GCCollect in your main loop? I recall reading somewhere that you should limit it to manually collect after intensive loops with buffer variables etc.. I can't say I have tried it though... |
| ||
As I said, it's only there for situations like this where I'm debugging. |
| ||
Erm, the destroy method itself references the Self object. It won't go out of scope until the method has returned. Or maybe I misunderstood something? |
| ||
That's the one I'm already accounting for. By my understanding Self objects aren't reference counted separately, only from the variable which initially calls them, which is why I described it was being held in a local variable which loops through a TMap. Wherever you consider it, I'm counting it. My question is why it isn't being collected when that one reference goes out of scope. |
| ||
How can you count the references to it without having a reference to it? |
| ||
You can't. That's why I've said twice now that it should be destroyed when the reference count hits one. Because I'm using a local variable to get that reference count and that will go out of scope almost immediately afterwards. |
| ||
You should find this interesting:Type TAnnoying Global list:TList = New TList Method New() list.AddLast(Self) Print "alive" EndMethod Method Kill() list.Remove(Self) DebugLog Byte Ptr(Self)[- 4] + " REFERENCES REMAIN" EndMethod Method Delete() Print "dead" EndMethod EndType Global k:TAnnoying = New TAnnoying k.Kill() k = Null GCCollect() EndIf k is Local then the reference never dies because k remains in scope until program termination. Which may or may not be a bug... |
| ||
That's because K isn't being constantly reassigned to a new object. As I said, the local variable which is holding the last reference to my object cycles through every object in a TMap, so it's constantly changing. It also has a completely different scope to my variable, which is within a function. I can only conclude that this hack to get BlitzMax's internal reference count is now broken. |
| ||
If you want an example of the scope I actually have and why it should be going out of scope, here is one. The scope of everything is identical.Type Test Field Name:String Field V:Int Global Map:TMap=New TMap Method Delete() RuntimeError "BING!" End Method Function Create(I:Int,sName:String) Local T:Test=New Test T.V=I T.Name=sName Map.Insert(T.Name,T) End Function Function Update() For Local E:Test=EachIn Map.Values() If E.V=2 E.Destroy() End If Next End Function Method Destroy() Map.Remove(Self.Name) End Method End Type Test.Create(1,"One") Test.Create(2,"Two") Test.Create(3,"Three") Test.Create(4,"Four") Test.Create(5,"Five") Test.Update() GCCollect Delay 1000 Note that the delete method here *is* called. |
| ||
If this is ever greater than one, there is a circular reference I haven't broken. Surely if you don't know if you've broken a circular reference or not, you don't really know how your program is working properly? |
| ||
Surely if you don't know if you've broken a circular reference or not, you don't really know how your program is working properly? What I know and what I can prove are two different things. If I post on here and say I know I'm breaking circular references, ten people will say "I bet you're not." If I show code which proves it, I hoped that could be avoided. |
| ||
it may or may not help, but something that i learned a while back is that GCCollect() is not immediate. http://www.blitzmax.com/Community/posts.php?topic=63550 |
| ||
Is that not the same thing that Fredborg posted? Your local is a global local ( local to the whole program, if you see what I mean ) whereas this is a function local, and the structure is as I showed above, which if you run it, does collect it. |
| ||
Now I'm completely confused... If it's collected then what is the problem you are having? |
| ||
It's not collected in my game. It is collected in the example above which uses the same way of triggering the destructor, since you were suggesting that the way I called the destructor might be causing the last reference to go out of scope. |
| ||
I spend more time chasing memory leaks now than before GC. |
| ||
Well I've honestly been pretty happy with the GC in general, but I've always felt it was underpowered, and needed improving. Weak references and the ability to retrieve a reference count are basic features of most managed-memory environments. Now we seem to be going backwards instead of forwards. Either the GC is broken or the reference count hack is broken. I don't much care which it is at this point, although the latter is obviously infinitely preferable in a wider sense, but either way, it makes the GC even less use than it was in the past, and it wasn't good enough in the first place, so it certainly can't afford to be getting worse. |
| ||
Have you a code showing the actual problem you are facing. Potentially you are using some chain calling to that method which might show similar issues to the ones suggested above but due to a different root. Haven't had any freeing problems so far I've to say. The only one I tend to run into quite too often is when I use TLinks on classes for fast removal from the list again and forget to make my destroy method null-ifying the tlink. in that case the object will never be freed. Oh and just to make sure: global variables operate on the reference count differently than locals ... means they don't imidiate free |
| ||
Have you a code showing the actual problem you are facing. No, and I'm afraid I don't have the luxury of time to be able to strip my game apart and figure out why the GC is spazzing over it. I know I'm handling my objects correctly, so either the object is being collected without the delete method being called ( if so, no big deal ) or the reference count hack is broken ( if so, no big deal ) or the GC is rarely failing to collect objects ( if so, it's probably rare enough that it won't matter, and the objects are not using much memory. ) Potentially you are using some chain calling to that method which might show similar issues to the ones suggested above but due to a different root. I'm afraid the chain is exactly as I showed it in the sample above, and it works in the sample above. I can't say why it doesn't work in the game, but I've wasted too much time trying to understand something which is never going to make sense, so I'm just going to have to trust that it won't become a bigger problem than it currently is. |
| ||
I found that if I had TLists in my classes, when I killed my class, sometimes the class would not get collected. The TLists should have gone out of scope and got collected, but they didn't. So, in my Destroy function, I erased any TLists. My classes then got collected. This didn't always happen, just sometimes. And it may be because I left a reference to the TList somewhere else (but I don't think so). Anyway, if you try clearing any TLists in your classes in the Destroy function, the problem may go away. |
| ||
Gabriel: I understand that you are frustrated. But I hope you understand as well that the hacks you used (we were talking on this topic when we were looking for ways of weak reference with auto collect when the library external references have been removed) might simply be interfering somewhere as well. [0] and [4] of objects are private data and not meant for you to be used nor can you trust the data which are in there. It was less of a problem in 1.24 but if you updated to 1.26 you will most likely run into bad problems as the reflection stuff access these fields as well. |
| ||
Hi, The GC mechanism is tricky, and the ref counts wont always give a clear indication of what's going on. The way the GC works, Max can optimize away pretty much all 'local' refs to objects, so a '1' ref count actually/usually implies there is a global (or field, or array element - ie: a memory ref) ref left. There are other states a ref count can be in too - if the high bit is set, then it's a 'floating' ref - there are no global refs left and it's on an internal stack for (possible) later collection. And this is all of course subject to change - if I get round to restoring the ability to debug while executing Delete() methods, this will probably require yet another internal stack somewhere. Play around with ref counts at your own peril! As always, the acid test is: does your program leak memory over time? |