As of Chrome 48, MemoryInfra supports heap profiling. The core principle is a solution that JustWorks™ on all platforms without patching or rebuilding, integrated with the chrome://tracing ecosystem.
Start Chrome with the --enable-heap-profiling
switch. This will make Chrome keep track of all allocations.
Grab a MemoryInfra trace. For best results, start tracing first, and then open a new tab that you want to trace. Furthermore, enabling more categories (besides memory-infra) will yield more detailed information in the heap profiler backtraces.
When the trace has been collected, select a heavy memory dump indicated by a purple dot. Heap dumps are only included in heavy memory dumps.
In the analysis view, cells marked with a triple bar icon (☰) contain heap dumps. Select such a cell.
Scroll down all the way to Heap Details.
Pinpoint the memory bug and live happily ever after.
By default heap profiling collects pseudo allocation traces, which are based on trace events. I.e. frames in allocation traces correspond to trace events that were active at the time of allocations, and are not real function names. It's also possible to use heap profiling with native, symbolized stack traces.
Using any officially distributed build of Chrome, navigate to chrome://flags, and set “enable-heap-profiling” to Enabled (native mode).
Use the TraceOnTap extension to grab a trace.
Run the following script to symbolize the trace.
third_party/catapult/tracing/bin/symbolize_trace <trace file>
Load the trace file in chrome://tracing
. Locate a purple dot, and continue from step 3 from the instructions above. Native stack traces will be shown in the Heap Details pane.
On Linux / Android, you need to build Chromium with special flags to use native heap profiling. On macOS / Windows, it's also possible to use native heap profiling with Chromium.
Build with the following GN flags:
macOS / Windows
symbol_level = 1
Linux
enable_profiling = true symbol_level = 1 Android arm_use_thumb = false enable_profiling = true symbol_level = 1
Start Chrome with --enable-heap-profiling=native
switch (notice =native
part).
On Android use the command line tool before starting the app:
build/android/adb_chrome_public_command_line --enable-heap-profiling=native
(run the tool with an empty argument ''
to clear the command line)
Grab a MemoryInfra trace. You don't need any other categories besides memory-infra
.
Save the grabbed trace file. This step is needed because freshly taken trace file contains raw addresses (which look like pc:dcf5dbf8
) instead of function names, and needs to be symbolized.
Symbolize the trace file. During symbolization addresses are resolved to the corresponding function names and trace file is rewritten (but a backup is saved with .BACKUP
extension).
Linux
third_party/catapult/tracing/bin/symbolize_trace <trace file>
Android
third_party/catapult/tracing/bin/symbolize_trace --output-directory out/Release <trace file>
(note --output-directory
and make sure it's right for your setup)
Load the trace file in chrome://tracing
. Locate a purple dot, and continue from step 3 from the instructions above. Native stack traces will be shown in the Heap Details pane.
The heap details view contains a tree that represents the heap. The size of the root node corresponds to the selected allocator cell.
The heap can be broken down in two ways: by backtrace (marked with an ƒ), and by type (marked with a Ⓣ). When tracing is enabled, Chrome records trace events, most of which appear in the flame chart in timeline view. At every point in time these trace events form a pseudo stack, and a vertical slice through the flame chart is like a backtrace. This corresponds to the ƒ nodes in the heap details view. Hence enabling more tracing categories will give a more detailed breakdown of the heap.
The other way to break down the heap is by object type. At the moment this is only supported for PartitionAlloc.
To keep the trace log small, uninteresting information is omitted from heap dumps. The long tail of small nodes is not dumped, but grouped in an <other>
node instead. Note that although these small nodes are insignificant on their own, together they can be responsible for a significant portion of the heap. The <other>
node is large in that case.
In the trace below, ParseAuthorStyleSheet
is called at some point.
The pseudo stack of trace events corresponds to the tree of ƒ nodes below. Of the 23.5 MiB of memory allocated with PartitionAlloc, 1.9 MiB was allocated inside ParseAuthorStyleSheet
, either directly, or at a deeper level (like CSSParserImpl::parseStyleSheet
).
By expanding ParseAuthorStyleSheet
, we can see which types were allocated there. Of the 1.9 MiB, 371 KiB was spent on ImmutableStylePropertySet
s, and 238 KiB was spent on StringImpl
s.
It is also possible to break down by type first, and then by backtrace. Below we see that of the 23.5 MiB allocated with PartitionAlloc, 1 MiB is spent on Node
s, and about half of the memory spent on nodes was allocated in HTMLDocumentParser
.
Heap dump diffs are fully supported by trace viewer. Select a heavy memory dump (a purple dot), then with the control key select a heavy memory dump earlier in time. Below is a diff of theverge.com before and in the middle of loading ads. We can see that 4 MiB were allocated when parsing the documents in all those iframes, almost a megabyte of which was due to JavaScript. (Note that this is memory allocated by PartitionAlloc alone, the total renderer memory increase was around 72 MiB.)