When profiling memory performance or investigating subtle memory leaks in macOS applications, high-level utilities like Activity Monitor and top show only coarse figures such as resident memory size. While low-level tools like vmmap report how virtual memory is partitioned across general MALLOC zones, they cannot answer the most vital developer question: which specific object classes and data structures are accumulating on the heap?
To inspect the actual contents of dynamic heap memory, macOS includes a powerful developer CLI diagnostic tool: heap. Located in /usr/bin/heap, this binary inspects the Mach-O heap of any running Objective-C, Swift, or C++ process, generating granular breakdowns of every allocated class instance, reference count, and byte volume. This guide explains how to execute, interpret, and automate heap analysis on macOS.
What the heap Utility Analyzes
In macOS, object-oriented applications allocate memory dynamically on the heap through runtime allocators (malloc() in C, alloc in Objective-C, and class initialisation in Swift). The heap tool traverses these allocation zones and provides:
- Class Instance Counts: The total number of living instances for every compiled class (e.g.,
NSString,NSData, custom Swift models). - Zone Aggregations: Total memory consumed by each individual class type across
MALLOC_TINY,MALLOC_SMALL, andMALLOC_LARGEzones. - Object Address Enumeration: The physical 64-bit virtual memory addresses of individual class allocations.
- Zone Fragmentation: Visibility into free blocks and memory fragmentation within heap regions.
Basic Syntax and Security Requirements
To run heap, supply the Process ID (PID) or process name of the target application:
heap <PID or Process Name>
Because inspecting another process’s heap involves attaching to its task port, macOS security policies (System Integrity Protection and Hardened Runtime) require root privileges via sudo:
sudo heap 1420
Filtering and Customising heap Output
1. Sorting by Total Memory Consumed
By default, heap outputs classes in alphabetical order. To immediately identify which objects are causing memory bloat, pass the -s (sort) flag with bytes:
sudo heap -s bytes Safari | head -n 30
This command lists the heaviest classes at the top of the report, instantly pinpointing whether image buffers, JSON models, or string arrays dominate memory.
2. Filtering for Specific Application Classes
System frameworks often allocate thousands of baseline objects (such as OS_dispatch_queue or CFString). To filter for your application’s proprietary class prefix, pipe the output through grep:
sudo heap 28410 | grep "MyCompany"
3. Listing Individual Object Memory Addresses
To print the exact hex memory address of every allocated instance of a specific class (which can subsequently be inspected in lldb or passed to malloc_history):
sudo heap -addresses "NSData" 1420
Interpreting heap Summary Output
A typical heap execution generates a structured table listing class allocations across all MALLOC zones:
| Output Column | Explanation | Diagnostic Significance |
|---|---|---|
| Class Name | The registered Objective-C, Swift, or C++ class identifier | Identifies the exact code abstraction occupying memory |
| Count | Total number of living, uncollected instances in the heap | An escalating count across user actions indicates a retain cycle |
| Bytes | Total cumulative byte footprint of all instances of this class | Highlights the highest-impact targets for memory optimisation |
| Average Size | Average byte payload per instance (Bytes / Count) |
Helps distinguish between small object leaks and giant payload buffers |
Real-World Example: Diagnosing a Retain Cycle
Suppose you are debugging a macOS document editing application. Users report that closing documents fails to release RAM. Run heap before and after opening and closing a document 5 times:
$ sudo heap -s count TextEditorPro | head -n 15
Class Name Count Bytes Avg Size
======================================== ====== ======= ========
TEPDocumentViewController 5 40,960 8,192
TEPUndoManager 5 12,800 2,560
TEPTextStorage 5 163,840 32,768
NSString 12,480 499,200 40
Notice that exactly 5 instances of TEPDocumentViewController remain resident in the heap despite all 5 documents being closed by the user! This clearly proves that strong reference cycles (such as un-invalidated delegates, notification observers, or timer blocks) are preventing the view controllers from deallocating.
Integrating heap with Other macOS Diagnostic Tools
- malloc_history: Take an object address from
heap -addresses <Class> <PID>and pass it tomalloc_history <PID> <Address>to view the full call stack that initially allocated that object. - leaks: Run
leaks <PID>to scan for orphaned heap allocations with no active incoming references. - vmmap: Run
vmmap -summary <PID>to verify whether heap growth correlates with physical dirty page increases.