< Previous PageNext Page > Hide TOC

Memory Management in Mac OS X

Efficient memory management is an important aspect of writing high performance code in Mac OS X code. Tuning your memory usage can reduce both your application’s memory footprint and the amount of CPU time it uses. In order to properly tune your code though, you need to understand something about how Mac OS X manages memory.

Unlike earlier versions of Mac OS, Mac OS X includes a fully-integrated virtual memory system that you cannot turn off. It is always on, providing up to 4 gigabytes of addressable space per 32-bit process and approximately 18 exabytes of addressable space for 64-bit processes. However, few machines have this much dedicated RAM for the entire system, much less for a single process. To compensate for this limitation, the virtual memory system uses hard disk storage to hold data not currently in use. This hard disk storage is sometimes called the “swap” space because of its use as storage for data being swapped in and out of memory.

Note: Unlike most UNIX-based operating systems, Mac OS X does not use a preallocated swap partition for virtual memory. Instead, it uses all of the available space on the machine’s boot partition.

The following sections introduce terminology and provide a brief overview of the Mac OS X virtual memory system. For more detailed information on how the Mac OS X virtual memory system works, please see Kernel Programming Guide.

Contents:

Virtual Memory Theory
Virtual Memory in Mac OS X


Virtual Memory Theory

Virtual memory allows an operating system to escape the limitations of physical RAM. A virtual memory manager creates a logical address space (or “virtual” address space) that is larger than the installed physical memory (RAM) and divides it up into uniformly-sized chunks of memory called pages. Each page in the logical address space has a corresponding page on the disk, in a special file known as the backing store. The system then populates the computer’s physical memory with the pages currently in use to give the illusion that the entire logical address space is made up of real memory.

There are two key features of the processor and its memory management unit (MMU) that you must grasp in order to understand how virtual memory works. The first is the page table, which is a table that maps all logical pages into their corresponding physical pages. When the processor accesses a logical address, the MMU uses the page table to translate the access into a physical address, which is the address that’s actually passed to the computer’s memory subsystem.

If the translation from a logical page address to a physical address fails, a page fault occurs. The virtual memory system invokes a special page-fault handler to stop executing the current code and respond to the fault. The page-fault handler finds a free page of physical memory, transfers the data from the backing store to the physical page, and then updates the page table so that the page now appears to be at the correct logical address. If no free pages are available in physical memory, the handler must first release an existing page. If that page contains modified data, the handler writes its contents to the backing store before releasing it. This process is known as paging.

Moving data from physical memory to disk is called paging out (or “swapping out”); moving data from disk to physical memory is called paging in (or “swapping in”). In both Mac OS 9 and Mac OS X, the size of a page is 4 kilobytes. Every time a page fault occurs, the system reads 4 kilobytes from disk. Extended periods of paging activity reduce performance significantly; such activity is sometimes called disk thrashing.

Reading from disk is much slower than reading directly from RAM, just as reading from RAM is always slower than reading directly from the CPU caches. Because a page fault involves reading data from disk, and potentially writing data to disk as well, reducing the number of occurring page faults can have a significant improvement on overall system performance.

Virtual Memory in Mac OS X

In Mac OS X, each process has its own sparse 32-bit or 64-bit virtual address space. For 32-bit processes, each process has an address space that can grow dynamically up to a limit of four gigabytes. For 64-bit processes, the address space can grow dynamically up to a limit of approximately 18 exabytes. As an application uses up space, the virtual memory system allocates additional swap file space on the root file system.

The virtual address space of a process consists of mapped regions of memory. Each region of memory in the process represents a specific set of virtual memory pages. A region has specific attributes controlling such things as inheritance (portions of the region may be mapped from “parent” regions), write-protection, and whether it is “wired” (that is, it cannot be paged out). Because regions contain a given number of pages, they are page-aligned, meaning the starting address of the region is also the starting address of a page and the ending address also defines the end of a page.

The kernel associates a VM object with each region of the virtual address space. The kernel uses the VM object to track and manage the resident and nonresident pages of that region. A region can map either to an area of memory in the backing store or to a specific file-mapped file in the file system.

The VM object maps regions in the backing store through the default pager and maps file-mapped files through the vnode pager. The default pager is a system manager that maps the nonresident virtual memory pages to backing store and fetches those pages when requested. The vnode pager implements file mapping. The vnode pager uses the paging mechanism to provide a window directly into a file. This mechanism lets you read and write portions of the file as if they were located in memory.

A VM object may point to a pager or to another VM object. The kernel uses this self referencing to implement a form of page-level sharing known as copy-on-write. Copy-on-write allows multiple blocks of code (including different processes) to share a page as long as none write to that page. If one process writes to the page, a new, writable copy of the page is created in the address space of the process doing the writing. This mechanism allows the system to copy large quantities of data efficiently.

Each VM object contains several fields, as shown in Table 1.

Table 1  Fields of the VM object

Field

Description

Resident pages

A list of the pages of this region that are currently resident in physical memory.

Size

The size of the region, in bytes.

Pager

The pager responsible for tracking and handling the pages of this region in backing store.

Shadow

Used for copy-on-write optimizations.

Copy

Used for copy-on-write optimizations.

Attributes

Flags indicating the state of various implementation details.

If the VM object is involved in a copy-on-write (vm_copy) operation, the shadow and copy fields may point to other VM objects. Otherwise both fields are usually NULL.

Page Lists in the Kernel

The kernel maintains and queries three system-wide lists of physical memory pages:

When the number of pages on the free list falls below a threshold (determined by the size of physical memory), the pager attempts to balance the queues. It does this by pulling pages from the inactive list. If a page has been accessed recently, it is reactivated and placed on the end of the active list. If an inactive page contains data that has not been written to the backing store recently, its contents must be paged out to disk before it can be placed on the free list. If an inactive page has not been modified and is not permanently resident (wired), it is stolen (any current virtual mappings to it are destroyed) and added to the free list. Once the free list size exceeds the target threshold, the pager rests.

The kernel moves pages from the active list to the inactive list if they are not accessed; it moves pages from the inactive list to the active list on a soft fault (see “Paging Virtual Memory In”). When virtual pages are swapped out, the associated physical pages are placed in the free list. Also, when processes explicitly free memory, the kernel moves the affected pages to the free list.

Allocating and Accessing Virtual Memory

Applications usually allocate memory using the malloc routine. This routine finds free space on an existing page or allocates new pages using vm_allocate to create space for the new memory block. Through the vm_allocate routine, the kernel performs a series of initialization steps:

  1. It maps a range of memory in the virtual address space of this process by creating a map entry; the map entry is a simple structure that defines the starting and ending addresses of the region.

  2. The range of memory is backed by the default pager.

  3. The kernel creates and initializes a VM object, associating it with the map entry.

At this point there are no pages resident in physical memory and no pages in the backing store. Everything is mapped virtually within the system. When a program accesses the region, by reading or writing to a specific address in it, a fault occurs because that address has not been mapped to physical memory. The kernel also recognizes that the VM object has no backing store for the page on which this address occurs. The kernel then performs the following steps for each page fault:

  1. It acquires a page from the free list and fills it with zeroes.

  2. It inserts a reference to this page in the VM object’s list of resident pages.

  3. It maps the virtual page to the physical page by filling in a data structure called the pmap. The pmap contains the page table used by the processor (or by a separate memory management unit) to map a given virtual address to the actual hardware address.

Paging Virtual Memory Out

The kernel continuously compares the number of physical pages in the free list against a threshold value. When the number of pages in the free list dips below this threshold, the kernel reclaims physical pages for the free list by swapping inactive pages out of memory. To do this, the kernel iterates all resident pages in the active and inactive lists, performing the following steps:

  1. If a page in the active list is not recently touched, it is moved to the inactive list.

  2. If a page in the inactive list is not recently touched, the kernel finds the page’s VM object.

  3. If the VM object has never been paged before, the kernel calls an initialization routine that creates and assigns a default pager object.

  4. The VM object’s default pager attempts to write the page out to the backing store.

  5. If the pager succeeds, the kernel frees the physical memory occupied by the page and moves the page from the inactive to the free list.

Paging Virtual Memory In

The final phase of virtual memory management moves pages in the backing store back into physical memory. A memory access fault initiates the page-in process. Memory access faults occur when code tries to access data at a virtual address that is not mapped to physical memory. There are two kinds of faults:

When any type of fault occurs, the kernel locates the map entry and VM object for the accessed region. The kernel then goes through the VM object’s list of resident pages. If the desired page is in the list of resident pages, the kernel generates a soft fault. If the page is not in the list of resident pages, it generates a hard fault.

For soft faults, the kernel maps the physical memory containing the pages to the virtual address space of the process. The kernel then marks the specific page as active. If the fault involved a write operation, the page is also marked as modified so that it will be written to backing store if it needs to be freed later.

For hard faults, the VM object’s pager finds the page in the backing store or from the file-mapped file, depending on the type of pager. After making the appropriate adjustments to the map information, the pager moves the page into physical memory and places the page on the active list. As with a soft fault, if the fault involved a write operation, the page is marked as modified.

Shared Memory

Shared memory is memory that can be written to or read from by two or more processes. Shared memory can be inherited from a parent process, created by a shared memory server, or explicitly created by an application for export to other applications. Uses for shared memory include the following:

Shared memory is fragile. If one program corrupts a section of shared memory, any programs that also use that memory share the corrupted data.

Wired Memory

Wired memory (also called resident memory) stores kernel code and data structures that should never be paged out to disk. Applications, frameworks, and other user-level software cannot allocate wired memory. However, they can affect how much wired memory exists at any time. There is memory overhead associated with each kernel resource expended on behalf of a program.

Table 2 lists some of the wired-memory costs for user-generated entities.

Table 2  Wired memory generated by user-level software

Resource

Wired Memory Used by Kernel

Process

16 kilobytes

Thread

blocked in a continuation—5 kilobytes; blocked—21 kilobytes

Mach port

116 bytes

Mapping

32 bytes

Library

2 kilobytes plus 200 bytes for each task that uses it

Memory region

160 bytes

Note: These measurements will change with each new Mac OS X release. They are provided here to give you a rough estimate of the relative cost of system resource usage.

As you can see, each thread created, each subprocess forked, and each library linked contributes to the resident footprint of the system.

In addition to wired memory generated through user-level requests, the following kernel entities also use wired memory:

Wired data structures are also associated with the physical page and map tables used to store virtual-memory mapping information, Both of these entities scale with the amount of available physical memory. Consequently, when you add memory to a system the wired memory increases even if nothing else changes. When the computer is first booted into the Finder, with no other applications running, wired memory consumes approximately 14 megabytes of a 64 megabyte system and 17 megabytes of a 128 megabyte system.

Wired memory is not immediately released back to the free list when it becomes invalid. Instead it is “garbage collected” when the free-page count falls below the threshold that triggers page out events.



< Previous PageNext Page > Hide TOC


© 2003, 2006 Apple Computer, Inc. All Rights Reserved. (Last updated: 2006-06-28)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.