Memory Management

Summary:
  • When you instantiate an object from a class, storage for the object is allocated from an area of memory.
  • The Curl® language has a garbage collection system that eliminates the task of micro-managing the memory use of your source code.
  • The weak pointer feature allows you to store data in memory while having it available for garbage collection and reallocation.

Dynamic Memory Allocation

Unprivileged Curl applets are limited to 1GB of memory. See System Restrictions. Within that limit, when you instantiate an object from a class, the memory to store the object is allocated from an area of memory. The amount of memory used for the object depends on the object's fields. For example, the object instantiated in the following source code requires at least enough memory to store an int and two chars:
{define-class public MemoryExample1
  field public a:int
  field public b:char
  field public c:char
}
let myobj:MemoryExample1={MemoryExample1}
The object reference variable that points to an instantiated object (such as myobj in the previous example) itself takes up memory to point to the memory address of the instantiated object. This is an important distinction. In the following class:
{define-class public MemoryExample2
  field public x:int
  field public z:MemoryExample1
}
The field z in the class definition is a reference to an object of the class MemoryExample1. An instance of MemoryExample2 will allocate memory to store an int and a object reference, not an entire instance of MemoryExample1.
The address space used by Curl applets is 32 bit on both 32 and 64 bit machines.

Deallocation via Garbage Collection

In some languages, such as C, you must explicitly allocate memory when creating a new object, and free the memory when your program is done with it. A lot of your programming time is spent in managing memory.
In these languages, failing to free some memory after your program is done with it results in a memory leak, where bits of memory are no longer used by the program but are not reclaimed and reused by the operating system. Eventually, memory leaks can cause the system to run out of memory; however, in the Curl language, the following code is perfectly safe:
{define-class public MyClass
  field public a:int
  field public b:char
  field public c:char
}

{value
    let myobj:MyClass={MyClass}
    set myobj={MyClass}
    set myobj={MyClass}
}
This code creates three objects of the class MyClass. The first two instances are unreferenced or inaccessible, since the object reference myobj no longer points to them. Therefore, the program has no way of regaining access to them. They have become garbage.
Fortunately, the Curl language has a garbage collection system. Periodically, the garbage collector scans the memory area, looking for memory that is no longer in use. Any memory that does not have at least one object reference pointing to it is reclaimed by the garbage collector so it can be reallocated later.
The garbage collection system frees you from the chore of having to micro-manage the memory use of your code. However, you need to consider two potential downsides of garbage collection:

Encouraging Garbage Collection

To address the issues mentioned above, you can use the garbage-collect procedure to suggest to the system that garbage collection take place at a certain point in your program. You might call garbage-collect after a piece of code that has used a lot of memory that it no longer needs, or before a speed-critical section where a garbage collection pause may cause a problem.
The following example demonstrates using garbage-collect. Clicking Make Garbage generates at least 1MB of garbage by instantiating and then losing many 1024-character arrays. Clicking Clean Garbage calls the garbage-collect procedure to clean up the mess the first button creates.
In order for you to see the effects of this example, you will need some way to monitor the memory usage on your system, such as the Task Manager window under Windows NT. Click Make Garbage and look for an increase in the Curl® Runtime Environment (RTE) memory use. Then click Clean Garbage to see its memory use decline again.

Example: Using garbage-collect
{value
    let make-garbage:CommandButton =
        {CommandButton label = "Make Garbage",
            {on Action do
                {for x = 1 to 1000 do
                    let a:{Array-of char} = {new {Array-of char}}
                    {a.set-size 1024, 'x'}
                }
            }
        }
    let clean-garbage:CommandButton =
        {CommandButton label = "Clean Garbage",
                {on Action do
                    {garbage-collect}
                }
        }
    {HBox make-garbage, clean-garbage}
}

Weak Pointers

When planning memory use in your applets written in the Curl® language, you may have data that would be useful but not absolutely vital to store in memory. By storing information in memory rather than having to reread it or recalculate it, your application will run faster. However, if the system is running low on memory, storing large amounts of data that may not be reused and can be regenerated if needed becomes a liability.

The Curl language's weak pointer feature lets you store data in memory, while having it available for garbage collection and reallocation if the garbage collector runs. In addition to collecting memory that is no longer used, the garbage collector also reclaims any memory that is only referenced by weak pointers.

To use weak pointers, you use the weak modifier when declaring the class field that you want to be weak:

{define-class public WeakData
  field private weak _myweakdata:#{HashTable-of String, String}
}
If the weak pointer's data is garbage collected, it will have a value of null. Therefore, you must always use the # operator to indicate the weak pointer can accept a value of null. The compiler will throw an error if you forget the #.
You should never store irreplaceable data in an object referenced by a weak pointer. Only use weak pointers with data that can be recalculated or otherwise recreated without too much overhead. When deciding whether to use a weak pointer to store data, you need to consider the tradeoff between possibly having to recalculate the data in the object again if it is garbage collected, and having the data take up space in memory that could be used for other, more vital information.

Working with Weak Pointer Data

If the data referenced by a weak pointer is garbage collected, its object reference variable is set to null. Before you try to access data referenced by only a weak pointer, you need to check the object reference to make sure the data is still there. If it is not, you will have to recreate the data. You should always mark fields that are weak pointers to be private, since their contents should only be read through an accessor.
Even after you check the object reference to ensure it is not null, the data could be garbage collected at any time while you are working with it. To prevent the garbage collector from collecting the data while you are accessing it, you should create a new, non-weak object reference pointing to the data. The non-weak pointer to the object prevents the garbage collector from collecting the garbage. When you are through accessing the cache data, you can eliminate the non-weak pointer (usually by exiting its scope, such as a method or a procedure). Once the non-weak reference to the data is gone, the data will again be available for garbage collection.
The safe way to access data referenced by a weak pointer is:

Weak Pointer Example

The following example demonstrates how you can use weak pointers to create a data cache class. The MyCache class contains a private field that is a weak reference to a HashTable-of Strings. A private method in the class, make-lots-of-data is responsible for filling the HashTable with whatever data it needs to contain. The constructor of the class simply calls this private method to set up the cache initially.
The class only allows access to the cached data via a method that takes a String parameter and returns the corresponding string in the HashTable. This method checks to see if the private cache field is null. If it is, then the cache has been garbage collected, and needs to be rebuilt by calling the make-lots-of-data method again.
{define-class public MyCache
  || The field that will hold the cached data
  field private weak _cached-data:#{HashTable-of String, String}
  || The private method that generates the cached data
  {method private {make-lots-of-data}:{HashTable-of String, String}
    || Lots of data generated here, stored in a HashTable and the
    || HashTable is returned.
  }

  || The constructor calls make-lots-of-data to have the cache
  || filled after the object is created. You could defer this to
  || be when the object is first accessed, as well.
  {constructor {default}
    || compute contents of cached-data by calling a private method
    set self._cached-data={self.make-lots-of-data}
  }

  || This method is called when people want to access the
  || cached data. Here, we accept a string and return the
  || associated string from the cached HashTable.
  {method public {get-cache-element index:String}:String
    || Create a temporary reference to the cached data that
    || isn't weak, to prevent the cache from being garbage
    || collected while it is being accessed.
    let tmp-cache-pointer:#{HashTable of String, String}=
        self._cached-data

    {if tmp-cache-pointer == null then
        || It has been garbage collected! Recalculate
        set tmp-cache-pointer = {self.make-lots-of-data}
        || Set the weak cache pointer to the new copy of the
        || cache
        set self._cached-data = tmp-cache-pointer
    }
    || Return the data from the cache. Of course, in actual use,
    || you would perform error checking here to make sure the element
    || exists in the cache! You could also add code to calculate
    || requested elements not already in the cache, and store the
    || results into the cache as well as returning them.
    {return {tmp-cache-pointer.get index}}
    || After exiting this method, the tmp-cache-pointer will no longer
    || exist, and therefore the cache data will again be available
    || for garbage collection.
  }
}