Skip to content

Execution and Lifecycle

datanorris edited this page Jun 8, 2016 · 11 revisions

Object Lifecycle and Finalizers

Ruby is a garbage-collected language. The garbage collector may run on any Ruby thread, either when Ruby chooses to invoke it directly before a memory allocation operation, or when it's explicitly invoked by Ruby code, the core library or an extension library. In the current implementation, the Ruby VM does not create any hidden Ruby threads for the purpose of garbage collection, finalizers or any other purpose.

Ruby allows you to register one or more finalizers to any value which is not an immediate value (i.e. object values only). A finalizer is a block which can be called when the object can be destroyed, and it is called with 1 parameter, which is the object id of the object which is being finalized.

An object id is currently implemented as a representation of a pointer to the object's memory location. As such they are a unique identifier for currently available objects - note that when an object is freed from memory e.g. by the garbage collector, it is possible for another object to be created with the same object ID.

Some types of Ruby core and extension library objects, e.g. certain I/O objects, also have a separate type of built-in internal finalizer which invokes externally visible actions, e.g. flushing buffers and closing file descriptors. Internal finalizers will not generally be discussed further here, but note that they are invoked at the time that the object is freed from memory, as described below.

Ruby has 2 mechanisms for invoking finalizers:

  • Garbage collection, as part of freeing an object
  • Ruby termination

The garbage collector frees objects as follows:

  • An object is deemed to be dead when it is no longer possible for any code to access the object. This includes finalizer code - if a registered finalizer block can access the object it is intended to finalize, e.g. through its closure on local variables, then the garbage collector will never free it
  • The garbage collector may choose to free dead objects when it is invoked
  • If the garbage collector chooses to free an object, then the object's finalizers will invoked as follows:
  • Any one Ruby thread will be selected to finalize the object, and it will run the object's finalizers in the order in which they were registered
  • Objects will be freed from memory before their associated finalizers are invoked
  • It is undefined whether the thread invoking the finalizer is holding any Mutex locks - if the finalizer locks Mutexes, it must be careful of deadlocking possibilities and similar issues e.g. that the thread it is running in does not already hold a lock it is attempting to acquire
  • Some more specific details of the current implementation of the garbage collector:
  • Ruby does not currently permit multiple finalizers to be running at once, either on multiple threads or reentrantly (a finalizer run within a finalizer)
  • When the garbage collector detects a dead object which has finalizers, the object will be added to a global list of objects with pending finalizers
  • During garbage collection, if there are pending finalizers to be run, and no finalizers are currently running, the thread running the garbage collector will be scheduled to execute all pending finalizers at its next interrupt point (as long as at that point no finalizers are running)
  • In an explicit invocation of the garbage collector, if no finalizers are currently running, pending finalizers will be run as part of the explicit invocation
  • If, after running a list of pending finalizers, there have been more pending finalizers added, these will immediately be run

When a Ruby application is in the process of terminating, Ruby will invoke finalizers as follows:

  • All registered finalizers will be run, in an undefined order, while the objects are still live (this includes finalizers which hold references to the object they are finalizing). Any registering or deregistering of finalizers during this process is not guaranteed to have any effect
  • Objects which have internal finalizers will have those finalizers invoked

If an exception is thrown out of a finalizer, it is silently caught and discarded (including, oh dear, thread kill and system exit exceptions!).

Tracing and Coverage

VM Initialization and Destruction

VM Irregular Termination

Unavailable resource exceptions

Clone this wiki locally