Mac OS memory management
The original problem for the engineers of the Macintosh was how to make optimum use of the 128 KB of RAM that the machine was equipped with. Since at that time the machine could only run one application program at a time, and there was no fixed secondary storage, the engineers implemented a simple scheme which worked well with those particular constraints. However, that design choice did not scale well with the development of the machine, creating various difficulties for both programmers and users.
The primary concern of the original engineers appears to have been fragmentation - that is, the repeated allocation and deallocation of memory through pointers leads to many small isolated areas of memory which cannot be used because they are too small, even though the total free memory may be sufficient to satisfy a particular request for memory. To solve this, Apple engineers used the concept of a relocatable handle, a reference to memory which allowed the actual data referred to be moved without invalidating the handle. Apple's scheme was simple - a handle was simply a pointer into a (non relocatable) table of further pointers, which in turn pointed to the data. If a memory request required compaction of memory, this was done and the table, called the master pointer block, was updated. The machine itself implemented two areas in the machine available for this scheme - the system heap (used for the OS), and the application heap. As long as only one application at a time was run, the system worked well. Since the entire application heap was dissolved when the application quit, fragmentation was minimized.
However, in addressing the fragmentation problem, all other issues were overlooked. The system heap was not protected from errant applications, and this was frequently the cause of major system problems and crashes. In addition, the handle-based approach also opened up a source of programming errors, where pointers to data within such relocatable blocks could not be guaranteed to remain valid across calls that might cause memory to move. In reality this was a problem in almost every system API that existed. Thus the onus was on the programmer not to create such pointers, or at least manage them very carefully. Since many programmers were not generally familiar with this approach, early Mac programs suffered frequently from faults arising from this - faults that very often went undetected until long after shipment.
Palm OS and 16-bit Windows use a similar scheme for memory management. However, the Palm and Windows versions make programmer error more difficult. For instance, in Mac OS to convert a handle to a pointer, a program just de-references the handle directly. However, if the handle is not locked, the pointer can become invalid quickly. Calls to lock and unlock handles are not balanced; ten calls to HLock are undone by a single call to HUnlock. In Palm OS and Windows, handles are an opaque type and must be de-referenced with MemHandleLock on Palm OS or Global/LocalLock on Windows. When a Palm or Windows application is finished with a handle, it calls MemHandleUnlock or Global/LocalUnlock. Palm OS and Windows keep a lock count for blocks; after three calls to MemHandleLock, a block will only become unlocked after three calls to MemHandleUnlock.
Addressing the problem of nested locks and unlocks can be straightforward (although tedious) by employing various methods, but these intrude upon the readability of the associated code block and require awareness and discipline on the part of the coder.
Memory leaks and stale references
Awareness and discipline are also necessary to avoid memory "leaks" (failure to deallocate within the scope of the allocation) and to avoid references to stale handles after release.
The situation worsened with the advent of Switcher, which was a way for the Mac to run multiple applications at once. This was a necessary step forward for users, who found the one-app-at-a-time approach very limiting. However, because Apple was now committed to its memory management model, as well as compatibility with existing applications, it was forced to adopt a scheme where each application was allocated its own heap from the available RAM. The amount of actual RAM allocated to each heap was set by a value coded into each application, set by the programmer. Invariably this value wasn't enough for particular kinds of work, so the value setting had to be exposed to the user to allow them to tweak the heap size to suit their own requirements. While popular among "power users", this exposure of a technical implementation detail was very much against the grain of the Mac user philosophy. Apart from exposing users to esoteric technicalities, it was inefficient, since an application would grab (unwillingly) all of its allotted RAM, even if it left most of it subsequently unused. Another application might be memory starved, but was unable to utilize the free memory "owned" by another application.
While an application could not beneficially utilize a sister application's heap, it could most certainly destroy it, typically by inadvertently writing to a nonsense address. An application treating a fragment of text or image, or an unassigned location as an address could easily overwrite the code or data of other applications or even the OS, leaving "lurkers" even after the program was exited. Such problems could be extremely difficult to analyze and correct.
Switcher became MultiFinder in System 4.2, which became the Process Manager in System 7, and by then the scheme was utterly entrenched. Apple made some attempts to work around the obvious limitations – temporary memory was one, where an application could "borrow" free RAM that lay outside of its heap for short periods, but this was unpopular with programmers so it largely failed to solve the problem. There was also an early virtual memory scheme, which attempted to solve the issue by making more memory available by paging unused portions to disk, but for most users with 68K Macintoshes, this did nothing but slow everything down without solving the memory problems themselves. Third party replacements for the Mac OS memory manager, such as Optimem, showed it could be done.
Originally the Macintosh had 128 kB of RAM, with a limit of 512 kB. This was increased to 4 MB upon the introduction of the Macintosh Plus. These Macintosh computers used the 68000 CPU, a 32-bit processor, but only had 24 physical address lines. The 24 lines allowed the processor to address up to 16 MB of memory (224 bytes), which was seen as a sufficient amount at the time. However, the RAM limit in the Macintosh design was 4 MB of RAM and 4 MB of ROM, because of the structure of the memory map. This was fixed by changing the memory map with the Macintosh II and the Macintosh Portable, allowing up to 8 MB of RAM.
Because memory was a scarce resource, the authors of the Mac OS decided to take advantage of the unused byte in each address. The original Memory Manager (up until the advent of System 7) placed flags in the high 8 bits of each 32-bit pointer and handle. Each address contained flags such as "locked", "purgeable", or "resource", which were stored in the master pointer table. When used as an actual address, these flags were masked off and ignored by the CPU.
While a good use of very limited RAM space, this design led to problems once Apple introduced the Macintosh II, which used the 32-bit Motorola 68020 CPU. The 68020 had 32 physical address lines and could address up to 4 GB (232 bytes) of memory. The flags that the Memory Manager stored in the high byte of each pointer and handle were significant now, and could lead to addressing errors.
In theory, the architects of the Macintosh system software were free to change the "flags in the high byte" scheme to avoid this problem, and they did. For example, on the Macintosh II, HLock() was rewritten to implement handle locking in a way other than flagging the high bits of handles. However, many Macintosh application programmers — and a great deal of the Macintosh system software code itself, even the code in the ROMs (until the IIci, which made the ROM 32-bit clean) – accessed the flags directly rather than using the APIs, such as HLock(), which had been provided to manipulate them. By doing this they rendered their applications incompatible with true 32-bit addressing, and this became known as not being "32-bit clean".
In order to stop continual system crashes caused by this issue, System 6 and earlier running on a 68020 or a 68030 would force the CPU into 24-bit mode, and would only recognize and address the first 8 megabytes of RAM, an obvious flaw in machines whose hardware was wired to accept up to 128 MB RAM – and whose product literature advertised this capability. With System 7, the Mac system software was finally made 32-bit clean, but there were still the problem of dirty ROMs. The problem was that the decision to use 24-bit or 32-bit addressing has to be made very early in the boot process, when the ROM routines initialized the Memory Manager to set up a basic Mac environment where NuBus ROMs and disk driver are loaded and executed. Older ROMs did not have this support and so was not possible to boot into 32-bit mode. Surprisingly, the first solution to this flaw was published by software utility company Connectix, whose 1991 product MODE32 reinitialized the Memory Manager and repeated early parts of the Mac boot process, allowing the system to boot into 32-bit mode and enabling the use of all the RAM in the machine. Apple licensed the software from Connectix later in 1991 and distributed it for free. The Macintosh IIci and later Motorola based Macintosh computers had 32-bit clean ROMs. (Even later models were based on the 64-bit AIM PowerPC processor and using New World ROM. The most recent models use 32-bit Intel x86-686 or 64-bit Intel x86-64 processors, connected to an EFI.)
However it was quite a while before applications were updated to remove all 24-bit dependencies, and System 7 provided a way to switch back to 24-bit mode if application incompatibilities were found. By the time of migration to the PowerPC and System 7.1.2, 32-bit cleanliness was mandatory for creating native applications.
The rise of object-oriented languages for programming the Mac – first Object Pascal, then later C++ – also caused problems for the memory model adopted. At first, it would seem natural that objects would be implemented via handles, to gain the advantage of being relocatable. However, these languages, as they were originally designed, used pointers for objects, which would lead to fragmentation issues. A solution, implemented by the THINK (later Symantec) compilers, was to use Handles internally for objects, but use a pointer syntax to access them. This seemed a good idea at first, but soon deep problems emerged, since programmers could not tell whether they were dealing with a relocatable or fixed block, and so had no way to know whether to take on the task of locking objects or not. Needless to say this led to huge numbers of bugs and problems with these early object implementations. Later compilers did not attempt to do this, but used real pointers, often implementing their own memory allocation schemes to work around the Mac OS memory model.
While the Mac OS memory model, with all its inherent problems, remained this way right through to Mac OS 9, due to severe application compatibility constraints, the increasing availability of cheap RAM meant that by and large most users could upgrade their way out of a corner. The memory wasn't used efficiently, but it was abundant enough that the issue never became critical. This is ironic given that the purpose of the original design was to maximise the use of very limited amounts of memory. OS X finally did away with the whole scheme, implementing a modern sparse virtual memory scheme. A subset of the older memory model APIs still exist for compatibility as part of Carbon, but map to the modern memory manager (a threadsafe malloc implementation) underneath. Apple recommends that OS X code use malloc and free "almost exclusively".
- Hertzfeld, Andy (September, 1983), The Original Macintosh: We're Not Hackers!, retrieved 2010-05-10
- Hertzfeld, Andy (January, 1982), The Original Macintosh: Hungarian, retrieved 2010-05-10
- memorymanagement.org (2000-12-15), Memory management in Mac OS, retrieved 2010-05-10
- Hertzfeld, Andy, The Original Macintosh: Mea Culpa, retrieved 2010-05-10
- Apple Computer (1985-10-01), Technical Note OV09: Debugging With PurgeMem and CompactMem, retrieved 2010-05-10
- Legacy Memory Manager Reference, Apple Inc, 2007-06-27, retrieved 2010-05-10
- Hertzfeld, Andy (October, 1984), The Original Macintosh: Switcher, retrieved 2010-05-10
- Mindfire Solutions (2002-03-06), Memory Management in Mac OS, p. 2, retrieved 2010-05-10
- "memory maps". Osdata.com. 2001-03-28. Retrieved 2010-05-11.
- Apple Computer (1991-01-01), Technical Note ME13: Memory Manager Compatibility, retrieved 2010-05-10
- Memory Allocation Recommendations on OS X, Apple Inc, 2005-07-12, retrieved 2009-09-22