Jump to page: 1 2
Thread overview
Understanding the GC
Jan 30, 2013
Jeremy DeHaan
Jan 30, 2013
Mike Parker
Jan 30, 2013
Mike Parker
Jan 30, 2013
monarch_dodra
Jan 30, 2013
Maxim Fomin
Jan 30, 2013
monarch_dodra
Jan 30, 2013
Maxim Fomin
Jan 30, 2013
monarch_dodra
Jan 31, 2013
Jeremy DeHaan
Feb 01, 2013
Jeremy DeHaan
Feb 01, 2013
monarch_dodra
Feb 01, 2013
monarch_dodra
Feb 01, 2013
FG
Feb 01, 2013
Sean Kelly
Jan 31, 2013
Sean Kelly
January 30, 2013
I've been reading TDPL book and was also reading some posts on these forums about the GC, but I wanted to clarify a few things to make sure I am understanding correctly.

From what I understand, when an object is recovered by the GC, the destructor may or may not be called. Why is that? Is it for performace reasons? What about the destructors of objects that the original object contained? Are they called when the item finally get's taken care of by the GC, or when the object is originally recovered by the GC?

My other question is, at the end of the program will GC go through every object it knows still holds memory and call their destructors? My guess is that it does so that the GC ensures all memory is successfully released before the program ends. I just want to make sure since in http://dlang.org/class.html, it says, "The garbage collector is not guaranteed to run the destructor for all unreferenced objects." What exactly is an unreferenced object?

Some other questions:

At what point is the GC called to do it's thing while the program is running? Is it at certain intervals or is it when the program's memory reaches a certain point?



Thanks very much for all the help!
January 30, 2013
On Wednesday, 30 January 2013 at 06:00:44 UTC, Jeremy DeHaan wrote:

>
> From what I understand, when an object is recovered by the GC, the destructor may or may not be called. Why is that? Is it for

That's not quite correct. When the object is collected, its destructor will be called. But you have no control over when or if it will happen during runtime. More on this below in relation to your question about unreferenced objects.


> performace reasons? What about the destructors of objects that the original object contained? Are they called when the item finally get's taken care of by the GC, or when the object is originally recovered by the GC?

Destructors of members will not be called when an object is collected. Only that of the object itself. But, there's no guarantee that any member references will still be valid when the object's destructor is called. See below for more.

>
> My other question is, at the end of the program will GC go through every object it knows still holds memory and call their destructors? My guess is that it does so that the GC ensures

Yes.

> all memory is successfully released before the program ends. I just want to make sure since in http://dlang.org/class.html, it says, "The garbage collector is not guaranteed to run the destructor for all unreferenced objects." What exactly is an unreferenced object?

A "referenced" object is one that is still in use by the program. Example:

auto mc = new MyClass;

// Here, there is exactly one reference to the MyClass instance
// and it is still active.

// But if i do this...
auto mc2 = mc;

// Now there are 2 references to the same instance.
// Set one to null.
mc = null;

// Now there is only one reference. Null it...
mc2 = null;

// Now all references to the original instance are invalid,
// so the object is unreachable by the program, so it is an
// unreferenced object.

The GC can only collect objects that it can be sure are unreferenced. Sometimes it can't be sure, in which case that particular object won't be collected during run time. For the objects that are determined to be collectable, there's no way to guarantee that any one of them will be collected in any given collection cycle. Because of that, you cannot rely on the order of destruction. Consider:

class Foo {}
class Bar { Foo f; }

Bar b = new Bar;
b.f = new Foo;

Given that b.f is the only reference to this Foo instance, then this instance of Foo will become unreferenced at the same time that the instance of Bar does. But, again, we cannot rely on the Bar instance being collected and destructed before the Foo instance. So if you have a Bar destructor that tries to access a member in f, then you would get a crash some of the time. Worse, what if there were more references to the Foo instance outside of Bar that haven't been released yet? If you tried to destroy f from the Bar destructor, or perhaps call some sort of cleanup method to release system resources that f controls, you'll get a crash somewhere else in the program when another bit of code tries to access the no longer valid reference. That would likely be a marathon debugging session, because most of the time you don't expect that sort of thing.

>
> Some other questions:
>
> At what point is the GC called to do it's thing while the program is running? Is it at certain intervals or is it when the program's memory reaches a certain point?

My understanding is that the current implementation only runs collections when memory is allocated. Meaning, when you allocate a new object instance, or cause memory to be allocated via some built-in operations (on arrays, for example), the GC will check if anything needs to be collected and will do it at that time. I don't know if it's run on every allocation, or only when certain criteria or met, and I really don't care. That's an implementation detail. The D language itself does not specify any of that.
January 30, 2013
The take-home point of all of this is that you shouldn't rely on destructors for resource deallocation. You could do it in by manually destructing objects when you are finished with them (via the destroy() method), but then you have to be extra careful about class members, ownership, order of destruction, and all of that. I find that it's much simpler if I give each of my objects a specific cleanup method that releases its own resources. When an object is no longer needed, I call its cleanup method and clear any references to it. This ensures that system resources or whatever are released in a predictable order and I don't run into odd crashes from trying to release something that's no longer valid. The GC takes care of the rest.
January 30, 2013
On Wednesday, 30 January 2013 at 08:15:15 UTC, Mike Parker wrote:
> On Wednesday, 30 January 2013 at 06:00:44 UTC, Jeremy DeHaan wrote:
>
>>
>> From what I understand, when an object is recovered by the GC, the destructor may or may not be called. Why is that? Is it for
>
> That's not quite correct. When the object is collected, its destructor will be called. But you have no control over when or if it will happen during runtime. More on this below in relation to your question about unreferenced objects.

To add to that, you also have to keep in mind that when the program terminates (even legally), instead of running a *full* collect cycle, the program just leaves, and lets the OS clear any allocated memory. This is both faster, and safer.

What this means is that while there is a guarantee that "collection=>destruction", there is no guarantee that actual collection will happen.

If you absolutely must be sure that something allocated gets *destroyed*, either destroy it yourself via an explicit call, or bind it to a stack based RAII scheme, possibly with reference counting.

>> performace reasons? What about the destructors of objects that the original object contained? Are they called when the item finally get's taken care of by the GC, or when the object is originally recovered by the GC?
>
> Destructors of members will not be called when an object is collected. Only that of the object itself. But, there's no guarantee that any member references will still be valid when the object's destructor is called. See below for more.

Just to be clear, I suppose you (both) are talking about "member references"? EG: Nested classes?

Destroying an object 100% guarantees its member destroyers are also called, outer to inner, first in first out, as part of the destruction process. The thing is that when you store a "class reference" then you call the destructor of the reference itself. References being glorified pointers, it basically means it does nothing.

//----
struct S{}
class  A{}

struct/class SA
{
    S s; //destroying this means ~S gets called.
    A a; //"destroying" this means doing "a  = null".
}
January 30, 2013
On Wednesday, 30 January 2013 at 10:29:26 UTC, monarch_dodra wrote:
> On Wednesday, 30 January 2013 at 08:15:15 UTC, Mike Parker wrote:
>> Destructors of members will not be called when an object is collected. Only that of the object itself. But, there's no guarantee that any member references will still be valid when the object's destructor is called. See below for more.
>
> Just to be clear, I suppose you (both) are talking about "member references"? EG: Nested classes?

I found calling member class references as nested classes confusing - they are very different.

> Destroying an object 100% guarantees its member destroyers are also called, outer to inner, first in first out, as part of the destruction process. The thing is that when you store a "class reference" then you call the destructor of the reference itself. References being glorified pointers, it basically means it does nothing.

Are you implying that in following code snippet:

class A { ~this() { ... } }
class B { A a; ~this() { ... } }

destructor of A will be always called after B, so "a" member is always accessible in B's dtor?
January 30, 2013
On Wednesday, 30 January 2013 at 11:57:01 UTC, Maxim Fomin wrote:
> On Wednesday, 30 January 2013 at 10:29:26 UTC, monarch_dodra wrote:
>> On Wednesday, 30 January 2013 at 08:15:15 UTC, Mike Parker wrote:
>>> Destructors of members will not be called when an object is collected. Only that of the object itself. But, there's no guarantee that any member references will still be valid when the object's destructor is called. See below for more.
>>
>> Just to be clear, I suppose you (both) are talking about "member references"? EG: Nested classes?
>
> I found calling member class references as nested classes confusing - they are very different.
>
>> Destroying an object 100% guarantees its member destroyers are also called, outer to inner, first in first out, as part of the destruction process. The thing is that when you store a "class reference" then you call the destructor of the reference itself. References being glorified pointers, it basically means it does nothing.
>
> Are you implying that in following code snippet:
>
> class A { ~this() { ... } }
> class B { A a; ~this() { ... } }
>
> destructor of A will be always called after B, so "a" member is always accessible in B's dtor?

No. What gave you that idea? I said the "reference" a will be destroyed (set to null). The actual referenced "class instance" may or may not be destroyed by then. The class instance of A itself is not a member of B.
January 30, 2013
On Wednesday, 30 January 2013 at 12:08:07 UTC, monarch_dodra wrote:
> On Wednesday, 30 January 2013 at 11:57:01 UTC, Maxim Fomin wrote:
>> On Wednesday, 30 January 2013 at 10:29:26 UTC, monarch_dodra wrote:
>>> On Wednesday, 30 January 2013 at 08:15:15 UTC, Mike Parker wrote:
>>>> Destructors of members will not be called when an object is collected. Only that of the object itself. But, there's no guarantee that any member references will still be valid when the object's destructor is called. See below for more.
>>>
>>> Just to be clear, I suppose you (both) are talking about "member references"? EG: Nested classes?
>>
>> I found calling member class references as nested classes confusing - they are very different.
>>
>>> Destroying an object 100% guarantees its member destroyers are also called, outer to inner, first in first out, as part of the destruction process. The thing is that when you store a "class reference" then you call the destructor of the reference itself. References being glorified pointers, it basically means it does nothing.
>>
>> Are you implying that in following code snippet:
>>
>> class A { ~this() { ... } }
>> class B { A a; ~this() { ... } }
>>
>> destructor of A will be always called after B, so "a" member is always accessible in B's dtor?
>
> No. What gave you that idea? I said the "reference" a will be destroyed (set to null). The actual referenced "class instance" may or may not be destroyed by then. The class instance of A itself is not a member of B.

English is not native for me. Sometimes non-natives misunderstand the meaning of the words.
January 30, 2013
On Wednesday, 30 January 2013 at 12:17:33 UTC, Maxim Fomin wrote:
> English is not native for me. Sometimes non-natives misunderstand the meaning of the words.

My apologies.
January 31, 2013
On Wed, 30 Jan 2013 03:15:14 -0500, Mike Parker <aldacron@gmail.com> wrote:

> My understanding is that the current implementation only runs collections when memory is allocated. Meaning, when you allocate a new object instance, or cause memory to be allocated via some built-in operations (on arrays, for example), the GC will check if anything needs to be collected and will do it at that time. I don't know if it's run on every allocation, or only when certain criteria or met, and I really don't care. That's an implementation detail. The D language itself does not specify any of that.

This isn't quite accurate.

The GC first checks to see if there is a free block that would satisfy the allocation, and if it can't find one, THEN it runs a collection cycle, and if then it cannot allocate the block from any memory regained, it then asks for more memory from the OS.

This can lead to the collection cycle running quite a bit when allocating lots of data.  I don't know if there are any measures to mitigate that, but there probably should be.

-Steve
January 31, 2013
GG.reserve can be handy for this. It tells the GC to pre allocate a block of memory from the OS.

On Jan 31, 2013, at 7:12 AM, "Steven Schveighoffer" <schveiguy@yahoo.com> wrote:

> On Wed, 30 Jan 2013 03:15:14 -0500, Mike Parker <aldacron@gmail.com> wrote:
> 
>> My understanding is that the current implementation only runs collections when memory is allocated. Meaning, when you allocate a new object instance, or cause memory to be allocated via some built-in operations (on arrays, for example), the GC will check if anything needs to be collected and will do it at that time. I don't know if it's run on every allocation, or only when certain criteria or met, and I really don't care. That's an implementation detail. The D language itself does not specify any of that.
> 
> This isn't quite accurate.
> 
> The GC first checks to see if there is a free block that would satisfy the allocation, and if it can't find one, THEN it runs a collection cycle, and if then it cannot allocate the block from any memory regained, it then asks for more memory from the OS.
> 
> This can lead to the collection cycle running quite a bit when allocating lots of data.  I don't know if there are any measures to mitigate that, but there probably should be.
> 
> -Steve
« First   ‹ Prev
1 2