 |
|
|
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.
|