3.6 Memory Management


3.6.1 Introduction

ECL relies on the Boehm-Weiser garbage collector for handling memory, creating and destroying objects, and handling finalization of objects that are no longer reachable. The use of a garbage collector, and in particular the use of a portable one, imposes certain restrictions that may appear odd for C/C++ programmers.

In this section we will discuss garbage collection, how ECL configures and uses the memory management library, what users may expect, how to handle the memory and how to control the process by which objects are deleted.


3.6.2 Boehm-Weiser garbage collector

First of all, the garbage collector must be able to determine which objects are alive and which are not. In other words, the collector must able to find all references to an object. One possibility would be to know where all variables of a program reside, and where is the stack of the program and its size, and parse all data there, discriminating references to lisp objects. To do this precisely one would need a very precise control of the data and stack segments, as well as how objects are laid out by the C compiler. This is beyond ECL’s scope and wishes and it can make coexistence with other libraries (C++, Fortran, etc) difficult.

The Boehm-Weiser garbage collector, on the other hand, is a conservative garbage collector. When scanning memory looking for references to live data, it guesses, conservatively, whether a word is a pointer or not. In case of doubt it will consider it to be a pointer and add it to the list of live objects. This may cause certain objects to be retained longer than what an user might expect but, in our experience, this is the best of both worlds and ECL uses certain strategies to minimize the amount of misinterpreted data.

More precisely, ECL uses the garbage collector with the following settings:

Except for finalization, which is a questionable feature, the previous settings are not very relevant for Common Lisp programmers, but are crucial for people interested in embedding in or cooperating with other C, C++ or Fortran libraries. Care should be taken when manipulating directly the GC library to avoid interfering with ECL’s expectations.


3.6.3 Memory limits

Beginning with version 9.2.1, ECL operates a tighter control of the resources it uses. In particular, it features explicit limits in the four stacks and in the amount of live data. These limits are optional, can be changed at run time, but they allow users to better control the evolution of a program, handling memory and stack overflow gracefully via the Common Lisp condition system.

The customizable limits are listed in Table 3.1, but they need a careful description.

If you look at Table 3.1, some of these limits may seem very stringent, but they exist to allow detecting and correcting both stack and memory overflow conditions. Larger values can be set systematically either in the ~/.eclrc initialization file, or using the command line options from the table.


3.6.4 Memory conditions

When ECL surpasses or approaches the memory limits it will signal a Common Lisp condition. There are two types of conditions, ext:stack-overflow and ext:storage-exhausted, for stack and heap overflows, respectively. Both errors are correctable, as the following session shows:

> (defun foo (x) (foo x))

FOO
> (foo 1)
C-STACK overflow at size 1654784. Stack can probably be resized.
Broken at SI:BYTECODES.Available restarts:
1. (CONTINUE) Extend stack size
Broken at FOO.
>> :r1
C-STACK overflow at size 2514944. Stack can probably be resized.
Broken at SI:BYTECODES.Available restarts:
1. (CONTINUE) Extend stack size
Broken at FOO.
>> :q
Top level.

3.6.5 Finalization

As we all know, Common-Lisp relies on garbage collection for deleting unreachable objects. However, it makes no provision for the equivalent of a C++ Destructor function that should be called when the object is eliminated by the garbage collector. The equivalent of such methods in a garbage collected environment is normally called a finalizer.

ECL includes a simple implementation of finalizers which makes the following promises.

The implementation is based on two functions, ext:set-finalizer and ext:get-finalizer, which allow setting and querying the finalizer functions for certain objects.


3.6.6 Memory Management Reference

Reference

Condition: ext:stack-overflow

Stack overflow condition

Class Precedence List

ext:stack-overflow, storage-condition, serious-condition, condition, t

Methods

Function: ext:stack-overflow-size condition
returns

A non-negative integer.

Function: ext:stack-overflow-type condition
returns

A symbol from Table 3.1, except ext:heap-size.

Description

This condition is signaled when one of the stack limits in Table 3.1 are violated or dangerously approached. It can be handled by resetting the limits and continuing, or jumping to an outer control point.

Condition: ext:storage-exhausted

Memory overflow condition

Class Precedence List

ext:storage-exhausted, storage-condition, serious-condition, condition, t

Description

This condition is signaled when ECL exhausts the ext:heap-size limit from Table 3.1. In handling this condition ECL follows this logic:

  • If the heap size limit was set to 0 (that is no limit), but there is some free space in the safety region ECL frees this space and issues a non-restartable error. The user may jump to an outer point or quit.
  • If the heap size had a finite limit, ECL offers the user the chance to resize it, issuing a restartable condition. The user may at this point use (ext:set-limit 'ext:heap-size 0) to remove the heap limit and avoid further messages, or use the (continue) restart to let ECL enlarge the heap by some amount.
  • Independently of the heap size limit, if ECL finds that there is no space to free or to grow, ECL simply quits. There will be no chance to do some cleanup because there is no way to allocate any additional data.
Function: ext:get-finalizer object
object

Any lisp object.

Description

This function returns the finalizer associated to an object, or nil.

Function: ext:get-limit concept
concept

A symbol.

Description

Queries the different memory and stack limits that condition ECL’s behavior. The value to be queried is denoted by the symbol concept, which should be one from the list: Table 3.1

Function: ext:set-finalizer object function

Associate a finalizer to an object.

object

Any lisp object.

function

A function or closure that takes one argument or nil.

Description

If function is nil, no finalizer is associated to the object. Otherwise function must be a function or a closure of one argument, which will be invoked before the object is destroyed.

Example

Close a file associated to an object.

(defclass my-class () ((file :initarg :file :initform nil)))

(defun finalize-my-class (x)
 (let ((s (slot-value x 'file)))
   (when s (format t "~%;;; Closing" s) (close s))))

(defmethod initialize-instance :around ((my-instance my-class) &rest args)
  (ext:set-finalizer my-instance #'finalize-my-class)
  (call-next-method))

(progn
  (make-instance 'my-class :file (open "~/.ecl.old" :direction :input))
  nil)

(si::gc t)
(si::gc t)

;; Closing
Function: ext:set-limit concept value

Set a memory or stack limit.

concept

A symbol.

value

A positive integer.

Changes the different memory and stack limits that condition ECL’s behavior. The value to be changed is denoted by the symbol concept, while the value is the new maximum size. The valid symbols and units are listed in Table 3.1.

Note that the limit has to be positive, but it may be smaller than the previous value of the limit. However, if the supplied value is smaller than what ECL is using at the moment, the new value will be silently ignored.

ConceptUnitsDefaultCommand line
ext:frame-stackNested frames2048--frame-stack
ext:binding-stackBindings8192
ext:c-stackBytes128 kilobytes--c-stack
ext:heap-sizeBytes256 megabytes--heap-size
ext:lisp-stackBytes32 kilobyes--lisp-stack

Table 3.1: Customizable memory limits