Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 1 - MacApp Theory and Architecture
Chapter 3 - Core Technologies


Dynamic Memory Allocation

The Macintosh user interface, the Macintosh Toolbox, and MacApp all encourage the use of dynamic memory allocation. For example, many applications allow the user to open multiple files and windows, limited only by the amount of available memory. Since the number of windows and files that can be opened on a specific machine cannot be known in advance, applications usually allocate these data structures from the heap.

The Macintosh Operating System may also use the application heap to allocate memory. It doesn't notify the application when it does so, and if there isn't enough space in the heap for the system's needs, the application may crash. Some of the common uses of the application heap by the operating system include

MacApp's memory management implements an overall strategy for working in such an environment. The goals of the strategy are twofold:

Permanent and Temporary Memory

MacApp's memory management scheme, implemented in the UMemory unit, attempts to ensure that critical memory requests always succeed. To do so, it defines two kinds of memory requests: temporary memory requests and permanent memory requests. A temporary memory request is a memory request that must succeed. For example, MacApp uses temporary requests to allocate memory for code segments and for Toolbox resources such as 'WDEF' and 'CDEF' resources.

A permanent memory request is a memory request that can be allowed to fail. Permanent requests are used for normal allocations, such as memory needed to open a document. Permanent allocations are not purged until you explicitly dispose of them. "Allocating Permanent and Temporary Memory," beginning on page 545, describes how your application can allocate temporary and permanent memory.

The naming of temporary and permanent requests can be confusing. Temporary might seem to imply that a memory request is transient and therefore less important. The exact opposite is true: MacApp makes sure temporary requests are always satisfied--only permanent requests can fail. However, a temporary allocation is temporary in the sense that it can be purged if necessary.

MacApp attempts to preserve enough space in the heap so that all temporary memory requests can be satisfied at any point in the program's execution. This ensures that requests that must succeed, such as code segment allocations, do succeed, while requests that you would merely like to succeed, such as allocation of data structures for a second (or later) document, may fail. If the temporary reserve is sufficiently large, your application will never fail when loading a code segment or system resource.

To satisfy a temporary memory request, MacApp may free other temporary items not currently in use. To keep track of temporary items, MacApp keeps four lists. The first two are used primarily by MacApp; the last two are intended for use by your application:

When there is not enough memory in the application heap to satisfy a memory request, MacApp's grow-zone function attempts to free a block of memory large enough to satisfy the request. The grow-zone function may purge items from the application's global lists, in this order:

  1. the gCodeSegs list (see page 68)
  2. the gApp1MemList list
  3. the gSysMemList list
  4. the gApp2MemList list

"The Grow-Zone Function," beginning on page 67, provides additional detail on MacApp's management of low-memory conditions. "Grow-Zone Hooks," beginning on page 68, describes the classes MacApp uses to keep lists of purgeable memory allocations.

Determining the Size of Memory Reserves

MacApp determines the size for a temporary memory reserve and for an additional, last-ditch low-space reserve using the following steps:

  1. During initialization of the application, MacApp determines the largest amount of temporary memory that is likely to be needed at one time while the application is running. The total includes the sum of

    • the size of all segments listed in 'seg!' resources (those segments likely to be loaded when the application is performing its most code-intensive operation)
    • any additional memory reserve specified in 'mem!' resources supplied by your application or MacApp; additions to the temporary memory reserve are specified in the third field of the 'mem!' resource
    • any processor-dependent temporary reserve specified in 'ppc!' (or '68k!' for 68K applications) resources (also stored in the third field)

      The temporary reserve total is stored in the global variable pSzTemporaryReserve.

  2. MacApp also determines the size for a last-ditch memory reserve, by adding the values stored in the fourth field of each 'mem!' and 'ppc!' (or '68k!' for 68K applications) resources, and stores it in the global variable pSzMemReserve.
  3. MacApp then allocates a temporary reserve, pTemporaryReserve, based on pSzTemporaryReserve, and a low-space reserve, pMemReserve, based on pSzMemReserve.

The 'seg!' resource is described on page 80. The 'mem!', '68k!', and 'ppc!' resources are described in Chapter 24, "Working With Memory and Failure Handling," beginning on page 551.

Figure 3-3 shows the application heap when the application is first initialized. The allocations that make up the temporary reserve are shaded. Note that the temporary reserve includes temporary handles and purgeable code segments.

Figure 3-3 The application heap, after initialization

As the user performs operations, the application may load and unload code segments and make other allocations. MacApp recalculates the temporary reserve at various times, such as when the operating system calls MacApp's grow-zone function (described in the next section). The temporary reserve is reduced by the size of any currently loaded, purgeable code segments in the gCodeSegs list and by the size of any purgeable handles in gSysMemList, gApp1MemList, and gApp2MemList (normally, all items in these lists are purgeable). These items can be purged, if necessary, to satisfy a temporary memory request, so their size can be considered part of the temporary reserve.

The Grow-Zone Function

A grow-zone function is a routine in your application that attempts to free memory in the application heap. Whenever the Memory Manager finds there isn't enough space in the heap to satisfy a memory request, it calls the grow-zone function to free additional space. It calls the grow-zone function repeatedly until there is enough free space to satisfy the request or until no more space can be freed, in which case the allocation request fails.

By installing its own grow-zone function, an application can help determine which requests succeed and which fail. MacApp installs a grow-zone function (named GrowZoneProc) as part of its overall strategy of memory management. MacApp's grow-zone function distinguishes between temporary and permanent memory requests. It won't free memory in the temporary reserve to satisfy a permanent request unless there is enough left over for any anticipated temporary requests. In this way there will always be enough space to satisfy temporary requests.

Note
The nature of a memory request, temporary or permanent, comes into play only when there is insufficient memory to satisfy the request, and the grow-zone function is called to free additional space. Otherwise, each request for memory, whether temporary or permanent, is satisfied from the application's available heap space, and no reorganization of the heap is necessary.

Grow-Zone Hooks

MacApp defines the CGrowZoneHook class to supply the grow-zone function with information about memory that can be purged in an emergency. MacApp also defines the following subclasses of CGrowZoneHook:

CCodeSegmentGrowZoneHook
A CCodeSegmentGrowZoneHook object manages a list of segments (used only in 68K-based applications). The gCodeSegmentGrowZoneHook global variable manages the gCodeSegs list (see page 64).
CMMHandleList
A CMMHandleList object manages a list of handles. The gApp1MemList and gApp2MemList global variables (page 64) are instantiated from this class.
CSysMemList
This subclass of CMMHandleList manages system handle data. The gSysMemList global variable (page 64) is instantiated from CSysMemList.
During application initialization, MacApp uses global variables to insert grow-zone hooks for the following lists, which may contain references to purgeable blocks of memory. When purging takes place, MacApp attempts to purge memory in the order in which the hooks are stored:

  1. the gCodeSegmentGrowZoneHook (which manages the gCodeSegs list)
  2. the gApp1MemList list
  3. the gSysMemList list
  4. the gApp2MemList list

Your application can use the classes listed above (or subclasses you define) to create its own lists of references to blocks of memory that may be purgeable. You tell MacApp's grow-zone procedure about your list by making a call like either of the following:

myMemList->InsertLast;// Insert last--free items in other lists first.
myMemList->InsertFirst;// Insert first--free items in this list first.
The grow-zone procedure's purging algorithms are described in the next section.

Purging Algorithms

The grow-zone function determines whether it has been called in response to a temporary or permanent request by examining the pTemporaryAllocation flag. Since system requests are temporary and tend to occur at unpredictable times, the normal state of this flag is TRUE, indicating that the next request should be temporary.

To release memory for a temporary memory request, MacApp's grow-zone routine uses the following algorithm:

To release memory for a permanent memory request, MacApp's GrowZoneProc uses the following algorithm:

Initializing MacApp's Memory Management

MacApp's memory management is initialized as part of its overall initialization. Your application either calls the InitUMacApp_Step1 routine (page 88) directly, or it calls the InitUMacApp macro, which in turn calls InitUMacApp_Step1. The InitUMacApp_Step1 routine calls UniversalStartup, which in turn calls ExpandHeap to perform the first part of MacApp's memory management initialization. ExpandHeap does the following:

Later in MacApp's initialization, the InitUMacApp_Step3 routine (see page 90) makes the following call:

InitUMemory(callsToMoreMasters);
The InitUMemory routine performs the following initialization tasks:

For more information on the memory management facilities provided by MacApp, see Chapter 24, "Working With Memory and Failure Handling."

Initializing MacApp's Segment Management

MacApp's segment management system, described in detail beginning on page 75, is part of memory management for 68K-based machines. Segment management is initialized from the InitUMacApp_Step3 routine, which makes the following call:

#if qSegments
   // Install MacApp's segment management system.
   InitUSegments();
#endif // qSegments
MacApp's segment management system is needed only for segmented 68K-based applications. MacApp uses the qSegments flag to control compilation of this code.

The InitUSegments routine performs the following initialization tasks:

Allocating Objects

One of the most common uses for dynamic memory allocation is to allocate the objects that make up your application. MacApp uses pointer-based objects (which point to nonrelocatable blocks of memory) rather than handle-based objects (which point, through a double-reference system, to relocatable blocks of memory).

Because most applications have many small objects, and because pointers don't move, they tend to fragment the heap. In addition, pointer allocation can be slow, because the Memory Manager will compact the entire heap to allocate a pointer with minimal fragmentation. However, MacApp provides a scheme to handle pointer-based object allocation efficiently and to avoid fragmentation, using a global object heap, gObjectHeap.

The storage space managed by the object heap is kept locked in memory and is accessed by pointers. During memory initialization, MacApp examines the 'mem!' resource to determine the initial size and the increment size for the object heap. The size of the heap is set to the sum of the values of the first field in each of the application's 'mem!' resources (with a default of 32 KB). The increment for growing the heap is set to the sum of the values of the second field in each 'mem!' resource (with a default increment of 30 KB).

These values are chosen to give reasonable results for a normal application. If your application creates a great number of objects and your application heap becomes fragmented, you may wish to modify your 'mem!' resource to specify a larger initial size or a larger increment, or both. However, it doesn't hurt to start with a smaller size, since the heap will grow when necessary, and experimentation has shown that an initial size of 32 KB is often more efficient than an initial size of 200 KB.

Creating an Object in the Object Heap

To create an object in the object heap, the MacApp class library defines two routines, operator new and operator delete, that replace the standard global operators defined by C++. MacApp's operator new and operator delete routines call MAOperatorNew and MAOperatorDelete, which in turn call the Allocate and Free functions of the ObjectHeap class.

This is the list of calls generated when an application creates a new object by name, using the new operator:

new TYourObject
   operator new(sizeof(TYourObject))
      MAOperatorNew(sizeof(TYourObject))
         MemoryHeap::Allocate(sizeof(TYourObject))
            ObjectHeap::DoAllocate(sizeof(TYourObject))
The ObjectHeap class descends from BestFitHeap, which in turn descends from MemoryHeap. The ObjectHeap::DoAllocate method directly allocates objects below a certain size, using a chunky approach: a chunk of memory large enough for a number of objects is allocated, then objects are handed out one at a time until a new chunk is needed. Allocated chunks are kept in a linear list.

The object heap calls BestFitHeap::DoAllocate to get memory for the chunks it uses and to try to satisfy requests it cannot handle. The BestFitHeap class uses an allocation mechanism that searches a binary tree of free blocks, ordered by size. If no free block is found, it expands the heap and repeats the search until the request is satisfied or until no more memory can be obtained.

Freeing an Object in the Object Heap

You call the MacApp routine FreeIfObject to free an object in the object heap (allocated with a new call). FreeIfObject generates the following list of calls:

FreeIfObject(aYourObject)
   aYourObject->Free()
      aYourObject->ShallowFree()
         operator delete(void* obj)
            MAOperatorDelete(void* obj)
               MemoryHeap::Free(void* blk)
                  ObjectHeap::DoFree(void* blk)
Eventually the block of memory is freed according to its type--either a block in a list of chunky blocks, or a block in the binary tree of blocks stored by BestFitHeap. The memory occupied by the object becomes available for reuse.

Recovering Freed Memory

In its current implementation, the object heap never shrinks. It starts out with a default size of 32 KB, and grows, if necessary, by a default increment of 30 KB. Memory allocated for the object heap is never again available to the application heap. When an object is freed, the memory it occupied is available to the object heap but the object heap itself doesn't shrink.

As a result, you should avoid situations where your application creates very large blocks of data in the object heap. Even though you free them after use, the object heap may grow to a size that interferes with other program operations. For very large memory requests, you can use the NewPermHandle routine to allocate a handle in permanent memory. After you free the handle, the memory is again available in the application heap.

Replacing MacApp's Global New and Delete Operators

When you create an object with new or delete an object with delete, by default you are using MacApp's global routines operator new (which calls MAOperatorNew) and operator delete (which calls MAOperatorDelete).

You can replace MacApp's global new and delete operators with the default C++ operators or with your own creation and deletion routines. To suppress inclusion of MacApp's global new and delete operators, you include the following directive on your MacApp build line:

-d qMAGlobalNew=FALSE
For more information on building MacApp, see Appendix A.

Since TObject has its own operator new and operator delete routines, it is possible to use different global new and delete operators without affecting allocation of MacApp objects that are based on TObject.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996