Oct 18 1999.

Visualworks space descriptions


Taken from:

ObjectMemory(class)>>spaceDescriptions

Included below is a brief description of the various spaces that make up object memory. With the exception of the two rembered tables (which grow and shrink automatically) and PermSpace (whose size can only be changed by performing a special kind of snapshot) this class includes methods for reading and setting the suggested initial sizes of each of these spaces:

NewSpace

NewSpace is used to house newly created objects. It is composed of three sub-spaces: an object-creation space (Eden) and two SurvivorSpaces. When an object is first created, it is placed in Eden. When Eden starts to fill up (i.e., when the number of used bytes in Eden exceeds the scavenge threshold), the scavenger is invoked and those objects housed in Eden and the occupied SurvivorSpace that are still reachable from the system roots are copied to the unoccupied Survivor Space. Thereafter, those objects that survive each scavenge will be shuffled by the scavenger from the occupied SurvivorSpace to the unoccupied one, until such time that the aggregate size of these survivors threatens to make the scavenge pause excessively long (i.e., when the number of used bytes in SurvivorSpace exceeds the tenure threshold), whereupon the scavenger will attempt to speed up subsequent scavenges by moving some of the older surviving objects from NewSpace to OldSpace. We say that such objects are being 'tenured' to OldSpace.

Remembered Table

The remembered table (RT) contains one entry for each object outside of NewSpace that is thought to contain a reference to an object housed in NewSpace. The objects in the RT are used as roots by the scavenger (i.e., if a NewSpace object is not transitively reachable from either the RT or the system's stacks, then it will not survive a scavenge). The RT is expanded and shrunk as needed by the virtual machine (VM); that is, the RT is expanded if the VM tries to store more entries than it can currently house, and it is shrunk during garbage collections if it has become both large and sparse, which can occur if a large number of entries were added and subsequently removed.

LargeSpace

Studies of object sizes show that there are only a few sources of large objects (i.e., objects greater than 1 KB in size) in the standard system--mainly cached bit maps and large text strings--but that enough of these objects are created dynamically that they must be handled specially (c.f. Ungar D. and Jackson F. 'An Adaptive Tenuring Policy for Generation Scavengers' Transactions on Programming Languages and Systems, Vol. 14, No 1. January 1992.). Certainly such objects should not be allocated in NewSpace along with ordinary objects, since they would fill up NewSpace prematurely, thus accelerating the scavenging rate and resulting in an increase in tenured garbage. In addition, the largest of these objects may actually be too large to fit into NewSpace. On the other hand, we don't want to simply allocate these objects in OldSpace, since their corpses could quickly consume all of our available address space if not reclaimed regularly.
Instead, we allocate these objects by placing their headers in NewSpace, while housing their data in a separate space known as LargeSpace. This arrangement permits the scavenger to scavenge large objects by simply manipulating the objects' headers without having to move the objects' data, thus making it relatively inexpensive to scavenge such objects. In fact, the data that is housed in LargeSpace is only moved when LargeSpace is compacted, either as part of a compacting garbage collection or by the object allocator to make room for another large object. In addition, if we choose not to tenure the headers of these objects (something that we can afford to do so long as LargeSpace is not in danger of overflowing or so long as these headers are not consuming a disproportionate amount of NewSpace), then any dead large objects will be reclaimed automatically by the scavenger.
In point of fact, LargeSpace is used to house the data of large byte objects (e.g., Strings, ByteArrays, UninterpretedBytes, etc.) as opposed to large pointer objects. We don't house the data of large pointer objects in LargeSpace because they would take up valuable space without saving the scavenger much work. That is, since their data is composed of oops, the scavenger has to scan their data anyway, so it's not that much more expensive to actually move the data while scanning it. Of course, the data of any object could be housed in LargeSpace, but small objects and large pointer objects are only placed in LargeSpace if there is no other place to house them.
When LargeSpace starts to run out of room, then the scavenger is informed that it should start to tenure the headers of large objects. During the next scavenge the headers of the oldest large objects will be tenured to OldSpace. However, the data of these large objects will not be moved from LargeSpace until the allocator actually runs out of space in LargeSpace. Only at that time will the data of these older large objects be moved to OldSpace in order to make room for the data of the newly created large object.

FixedSpace

Because the garbage collector can and does move objects passing objects to foreign code presents difficulties. For example, if foreign code needs to hold-on to an object it must do so through some fixed key that does not change. Often foreign code only needs access to the contents of a non-pointer object, such as a ByteArray, for use as a buffer. Such use does not require that an object's oop remain constant, but it does require that an object's body remains at a fixed address. One can use DLLCC's C facilities to manipulate C data on the C heap, which does not move. But this can be tedious; one must use malloc and slow indirection facilites, and take special care to ensure garbage is collected. FixedSpace provides a much more convenient mechanism. FixedSpace can house the bodies of any non-pointer objects. When an object is passed to foreign code running in a separate thread, i.e. via DLLCC's _threaded mechanism, the argument marshalling code automatically promotes the object's body to FixedSpace. The object's body will never move through-out its life-time. But the spoace it occupies will be returned to the FixedSpace pool when it is garbage collected. Note that because objects in FixedSpace cannot move, FixedSpace cannot be compactewd and will therefore quickly become fragmented. So FixedSpace should be used carefully. Note that FixedSpace is coallesced at image start-up. So FixedSpace can be compacted by saving a snapshot, quitting, and then restarting.
On platforms that support it, new FixedSpace segments are mapped into the address space, and may be subsequently unmapped, freeing the memory they occupy, and reducing total memory usage. But only empty FixedSpace segments can be unmapped. Further, the engine can only free entire segments so do not expect to get exactly the amount of shrinkage you ask for. Since FixedSpace cannot be compacted it is extremely unlikely that garbage collection will free all the objects in any FixedSpace segment, so do not expect to be able to shrink FixedSpace.

OldSpace

Although OldSpace can be thought of as a single contiguous chunk of memory, it is currently implemented, not as a single contiguous segment residing on the system's heap, but as linked list of segments occupying the upper portion of the heap. The fact that OldSpace has to be able to grow on demand rules out the possibility of it actually occupying a single contiguous segment, since, for example, I/O routines frequently allocate portions of the heap for their own use, thus creating 'sandbars' which divide OldSpace into separate segments.
Each OldSpace segment is composed of two parts--an Object Table (OT) that is used to house the old objects' headers and a data heap that is used to house the objects' data. The data heap is housed at the bottom of the segment and grows upwards, and the OT is housed at the top of the segment and grows downward. Both the OT and the data heap are compacted by the compacting garbage collector. However, in the interest of speed, the compact-data primitive only compacts the data, not the OT.
Since the OT and the data heap grow towards each other (thereby consuming the same block of contiguous free space from different directions), the user should never run out of space for new object headers while still having plenty of space for object data and vice versa. Nor is there any arbitrary limit to the total size of OldSpace, the total size of a given OldSpace segment, or the number of OldSpace segments that one can acquire. The only memory-related resource that you can run out of is address space--on real memory machines this translates to available real memory and on virtual-memory machines this translates to available swap space.
In addition, the system maintains a threaded list of free OT Entries (OTEs) and a threaded free list of free data chunks. The incremental garbage collector recycles objects by placing their headers and bodies on these lists, and the OldSpace allocator tries to allocate objects by utilizing the space on these lists before dipping into the free contiguous space between the OT and the data heap of each segment. Finally, a certain portion of the free contiguous data is reserved for use by the VM in order to ensure that it can perform at least one scavenge in extreme low-space conditions, thereby providing the system with one final opportunity to take the appropriate action.
On platforms that support it, new OldSpace segments are mapped into the address space, and may be subsequently unmapped, freeing the memory they occupy, and reducing total memory usage. The compactor can compact headers and data across segments, and hence can completely vacate segments. Such empty segments can be unmapped. There are currently no facilities which can be used to ask the compactor which segments to vacate by preference. A compacting garbage collection followed by a request to shrink memory will only succeed if new OldSpace segments can be memory-mapped and at least one empty mapped segment exists after collection. Further, the engine can only free entire segments so do not expect to get exactly the amount of shrinkage you ask for.

PermSpace

PermSpace is used to hold all quasi permanent objects. That is, objects housed in PermSpace are exempt from being reclaimed by any of the reclamation facilities other than the global garbage collector. Since the objects housed in PermSpace almost never die, it is generally wasteful to have the incremental garbage collector and the compacting garbage collector expend time fumbling over such objects. By exempting such objects from reclamation, the time required to reclaim the garbage that may be present in OldSpace is reduced many times. Of course, dead PermSpace objects can still be reclaimed by invoking the global garbage collector. In general, the only time you would want to invoke the global garbage collector would be if you suspected that there were many garbage objects in PermSpace and you wanted to either reduce the size of the image files produced by subsequent snapshots or you wanted to reclaim the space occupied by the garbage objects in OldSpace, NewSpace, or LargeSpace that are transitively reachable by the garbage objects housed in PermSpace and hence exempt from collection by the system's non-global reclamation facilities.
The standard system comes with a fully stocked PermSpace. In fact, most of the objects present in the standard system are housed in PermSpace. You can add objects to PermSpace by performing a special kind of snapshot that we call a perm-save (choose the 'Special->perm save as...' selection from the Launcher). Saving an image in this manner is similar to performing a normal snapshot except that all of the objects that are currently in OldSpace are promoted to PermSpace as they are written out to disk. Note that the current state of object memory is not changed by the act of perm-saving an image. In other words, only the perm-saved image will contain the additional objects in PermSpace. This means that if you perm-save an image and later in that same session you snapshot the current state of object memory on top of the previously perm-saved image, then the objects housed in the image's PermSpace will not include any additional objects.
Currently the perm-save-as protocol promotes all OldSpace objects to PermSpace. Accordingly, some of these objects may expire shortly after the perm-saved image is invoked. We recommend, then, that developers perform the following steps prior to distributing a newly perm-saved image:

1. Once you have created all of the objects that are potentially permanent (classes, methods, objects that comprise essentially static data, etc.), you should perform a perm-save.

2. You should then invoke the perm-saved image and either explicitly invalidate all non-permanent objects that were promoted to PermSpace or you should exercise the system in such a way that these ephemeral objects become garbage (in many cases, it may be sufficient to simply start up the system).

3. You should then reclaim these objects by performing a global garbage collection followed by a conventional snapshot. The snapshot will not write out any PermSpace objects that have been reclaimed.

4. You may then want to perform some platform-specific customization. You should do so by loading the system onto the platform of choice, and after performing the appropriate customization, you should make one last conventional snapshot.

Developers who distribute code in source-code form or in BOSS format should either urge their customers to perform the steps outlined above, or the developer can provide a script that will perform these steps when their code is loaded. In any case, you should avoid perm-saving an image if OldSpace contains lots of data that is known to be temporary. However, if such objects do get promoted to PermSpace when you perm-save an image, they can be removed after they become garbage by simply performing a global garbage collection followed by a conventional snapshot. In addition, you can move all PermSpace objects back to OldSpace by performing another kind of special snapshot that we call perm-undoing (choose the 'Special->perm undo as...' selection from the Launcher). A perm-undo image will have an empty PermSpace, and all objects that were formerly in PermSpace will now be in OldSpace. Once again, this change only applies to the snapshot file. The contents of PermSpace in your running system will remain unchanged.

Old Remembered Table

The old remembered table (oldRT) contains one entry for each object in PermSpace that is thought to contain a reference to an object housed in OldSpace. The objects in the oldRT are used as roots by the incremental garbage collector and the compacting garbage collector (i.e., if an OldSpace object is not transitively reachable from the oldRT, then it will not survive a garbage collection). The oldRT is expanded and shrunk as needed by the VM; that is, the oldRT is expanded if the VM tries to store more entries than it can currently house and it is shrunk if it has become both large and sparse, which can occur if a large number of entries were added and subsequently removed.

StackSpace

Smalltalk activation records are kept in two forms: as context objects housed in object memory and as stack frames housed in StackSpace. When a Smalltalk method is being executed, its activation record must be in frame format. This frame format is designed to mate well with the platform's subroutine call instruction. When one of the fields of an activation record is accessed by Smalltalk, however, the activation record must be turned into a first-class context object. This conversion from frame format to context object and vice versa is done automatically by the VM. StackSpace is used to house the frames of those activation records that are currently active. The StackSpace is just a cache, however. If there are more activation records than there is room to house them in StackSpace, then the VM will simply convert some of the stack frames to context objects and house them in object memory.

CompiledCodeCache

To avoid the overhead of interpretation, the VM does not interpret the standard Smalltalk bytecode set. Instead, it executes a given Smalltalk method only after that method has been compiled into the platform's machine code. This machine-code generation is performed automatically by the VM, and the resulting machine code is then placed in the CompiledCodeCache.
For example, when a Smalltalk method is executed for the first time, it is automatically compiled into machine code by the VM, and the resulting machine-code version of that method is then cached in the CompiledCodeCache so that it can be executed. Once executed, this method's machine code is left in the cache for subsequent execution.
As its name suggests, this zone is only used as a cache since it would require an excessive amount of memory to permanently house the machine-code version of every Smalltalk method. If the cache begins to overflow, those methods that have not been executed recently are simply flushed from the cache. This approach gives Smalltalk much of the speed that comes with executing compiled code, and most of the space savings and all of the portability that come with interpretation.