This site has been retired. For up to date information, see handbook.gnome.org or gitlab.gnome.org.


[Home] [TitleIndex] [WordIndex

Vala's Memory Management Explained

Vala's memory management is based on automatic reference counting instead of tracing garbage collection.

This has both advantages and disadvantages. Reference counting is deterministic, but you can form reference cycles in some cases. In these cases you must use weak references in order to break those cycles. The Vala keyword for this is weak.

This is how automatic reference counting works:

Each time a reference type object is assigned to a variable (referenced) its internal reference count is increased by one (ref), each time a reference variable goes out of scope the object's internal reference count is decreased by one (unref).

If an object's internal reference count reaches 0 the object gets freed. When an object is freed each of its reference type data members' reference count is decreased by one (unref). If one of these reaches 0 it gets freed with the same procedure, and so on.

But what is a reference cycle and why is it bad? Let's have a look at a simple doubly-linked list implementation without data:

class Node : Object {
    public Node prev;
    public Node next;

    public Node (Node? prev = null) {
        this.prev = prev;      // ref
        if (prev != null) {
            prev.next = this;  // ref
        }
    }
}

void main () {
    var n1 = new Node ();    // ref
    var n2 = new Node (n1);  // ref

    // print the reference count of both objects
    stdout.printf ("%u, %u\n", n1.ref_count, n2.ref_count);

}   // unref, unref

The places where referencing and unreferencing happens are commented for your better understanding in this example. The following figure illustrates the situation after n1 and n2 are assigned:

refcycle1.png

Each arrow (edge) in the graph represents a reference and each node has a number indicating the object's reference count (the number of arrows pointing at it). null references are not shown. You can easily spot the reference cycle (a roundtrip by following the arrows). After n1 and n2 are unrefed at the end of the block both nodes will have a reference count of 1, but none of them will reach 0 and be freed.

In our case we're lucky, because the program terminates anyway and the operating system will free all memory of the process anyway. But what would happen if the program ran longer? For example:

void main () {
    while (true) {
        var n1 = new Node ();
        var n2 = new Node (n1);
        Thread.usleep (1000);
    }
}

Open a task manager / process monitor (e.g. gnome-system-monitor) and start the program. You can watch it eating up your memory. Break it before it slows down your system. (The memory will be freed immediately.)

An equivalent C# or Java program would have no problem, because a tracing garbage collector regularly throws away all objects that are no longer reachable, either directly or indirectly, from the current scope. But in Vala you have to take a countermeasure in the case of a reference cycle.

We can break the cycle by making one of the references in the cycle a weak reference:

    public weak Node prev;
    public Node next;

This means assignment to such a variable won't increase the object's reference count. Now the situation looks like this:

refcycle2.png

The reference count of the first object is 1, not 2 as it was before. When n1 and n2 go out of scope at the end of the block both objects' reference counts are decreased by one. The first object is freed, because its reference count hits 0, the second one will have a reference count of 1. However, because the first object held a reference to the second object the reference count of the second object is decreased again, hits 0 and the second object gets freed as well.

Run the program again and you will see that the memory consumption stays stable.

A reference cycle does not necessarily have to be a direct cycle. This is a reference cycle as well:

refcycle3.png

Unowned References

All objects of Vala classes and most objects of gobject-based libraries are reference counted. However, Vala allows you to use classes of non-gobject-based C libraries that have no reference counting support by default, too. These classes are called compact classes (annotated with the [Compact] attribute).

Non reference counted objects may have only one strong reference (think of it as the "owning" reference). When this reference goes out of scope the object is freed. All other references must be unowned references. When these references go out of scope the object will not be freed.

unowned-reference.png

So when you call a method (most likely of a non-gobject library) that returns an unowned reference (this is marked on the method's return type with the unowned keyword) to an object that you are interested in, then you have two choices: either copy the object if it has a copy method, then you can have your own single strong reference to the new copied object, or assign the reference to a variable that is declared with the unowned keyword and Vala will know that it should not free the object on your side.

Vala prevents you from assigning an unowned reference to a strong (i.e. not unowned) reference. However, you can transfer the ownership to another reference with (owned):

[Compact]
class Foo { }

void main () {
    Foo a = new Foo ();
    unowned Foo b = a;
    Foo c = (owned) a;   // 'c' is now the new "owning" reference
}

ownership-transfer.png

By the way, Vala strings are not reference counted, too, because they are based on the C type char*. However, Vala takes care of automatically copying them when needed. So you actually don't have to think about them. Only writers of bindings have to take care of whether a string reference is unowned or not and mark it in the API as appropriate.

Currently many types in the Vala binding APIs are falsely marked weak even though they should actually be unowned. This is because in the past there was only the weak keyword for both, so don't let this confuse you. At the moment weak and unowned can be used interchangeably. However, you should use weak only for breaking reference cycles and unowned only for ownership issues as described obove.

Memory Management by Example

Normal, reference-counted classes

public class Foo {
    public void method () { }
}

void main () {
    Foo foo = new Foo ();    // allocate, ref
    foo.method ();
    Foo bar = foo;           // ref
}  // unref, unref => free

Everything is managed automatically.

Manual memory management with pointer syntax

You can always choose to do manual memory management if you feel as if you must have full control. The pointer syntax is similar to C/C++:

void main () {
    Foo* foo = new Foo ();   // allocate
    foo->method ();
    Foo* bar = foo;
    delete foo;              // free
}

Compact classes without reference counting

Compact classes are classes that are not registered with Vala's type system. They are usually classes bound from non gobject-based C libraries. However, you can also define your own compact classes in Vala if you can live with the fact that they are very limited feature-wise (e.g. no inheritance, no interface implementation, no private fields, etc.).

Creating and destroying compact classes is faster than non-compact classes (about 2.5 times faster than regular classes, and an order of magnitude faster than GObject-derived classes), though other operations are no different. That said, modern hardware can create and destroy millions of GObject-derived classes per thread per second, so it is advisable to make sure this is a performance bottleneck before trying to optimize it away.

Compact classes do not support reference counting by default. So there can be only one "owning" reference that will cause the object to be freed when it goes out of scope, all other references have to be unowned.

[Compact]
public class Foo {
    public void method ();
}

void main () {
    Foo foo = new Foo ();    // allocate
    foo.method ();
    unowned Foo bar = foo;
    Foo baz = (owned) foo;   /* ownership transfer: now 'baz' is the "owning"
                                reference for the object */
    unowned Foo bam = baz;
} // free ("owning" reference 'baz' went out of scope)

Compact classes with reference counting

You can add reference counting to a compact class by implementing it manually. You have to tell Vala which functions it should use for referencing and unreferencing with a CCode attribute.

[Compact]
[CCode (ref_function = "foo_up", unref_function = "foo_down")]
public class Foo {

    public int ref_count = 1;

    public unowned Foo up () {
        GLib.AtomicInt.add (ref this.ref_count, 1);
        return this;
    }

    public void down () {
        if (GLib.AtomicInt.dec_and_test (ref this.ref_count)) {
            this.free ();
        }
    }

    private extern void free ();
    public void method () { }
}

void main () {
    Foo foo = new Foo ();    // allocate, ref
    foo.method ();
    Foo bar = foo;           // ref
} // unref, unref => free

As you can see, everything is managed automatically again.

Immutable compact classes with a copy function

If a compact class does not have reference counting support but is immutable (its internal state doesn't change) and has a copy function that duplicates the object then Vala will copy it automatically if it's assigned to a strong reference.

[Compact]
[Immutable]
[CCode (copy_function = "foo_copy")]
public class Foo {
    public void method () { }

    public Foo copy () {
        return new Foo ();
    }
}

void main () {
    Foo foo = new Foo ();   // allocate
    foo.method ();
    Foo bar = foo;          // copy
} // free, free

If you want to prevent copying for fine tuning reasons you can still use an unowned reference:

void main () {
    Foo foo = new Foo ();   // allocate
    foo.method ();
    unowned Foo bar = foo;
} // free


2024-10-23 11:37