blob: a353cfd9c26eb18b8170d068baed02e33c0c0f7b [file] [log] [blame]
page.title=Debugging ART Garbage Collection
@jd:body
<!--
Copyright 2015 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div id="qv-wrapper">
<div id="qv">
<h2 id="Contents">In this document</h2>
<ol id="auto-toc">
</ol>
</div>
</div>
<p>This document describes how to debug Android Runtime (ART) Garbage Collection
(GC) correctness and performance issues. It explains how to use GC verification
options, identify solutions for GC verification failures, and measure and
address GC performance problems.</p>
<p>See <a href="index.html">ART and Dalvik</a>, the <a
href="dex-format.html">Dalvik Executable format</a>, and the remaining pages
within this <a href="index.html">ART and Dalvik</a> section to work with
ART. See <a
href="http://developer.android.com/guide/practices/verifying-apps-art.html">Verifying
App Behavior on the Android Runtime (ART)</a> for additional help verifying app
behavior.</p>
<h2 id=art_gc_overview>ART GC overview</h2>
<p>ART, introduced as a developer option in Android 4.4, is the default Android
runtime for Android 5.0 and beyond. The Dalvik runtime is no longer maintained
or available and its byte-code format is now used by ART. Please note this
section merely summarizes ART’s GC. For additional information, see the <a
href="https://www.google.com/events/io/io14videos/b750c8da-aebe-e311-b297-00155d5066d7">Android
runtime</a> presentation conducted at Google I/O 2014. </p>
<p>ART has a few different GC plans that consist of running different garbage
collectors. The default plan is the CMS (concurrent mark sweep) plan, which
uses mostly sticky CMS and partial CMS. Sticky CMS is ART’s non-moving
generational garbage collector. It scans only the portion of the heap that was
modified since the last GC and can reclaim only the objects allocated since the
last GC. In addition to the CMS plan, ART performs heap compaction when an app
changes process state to a jank-imperceptible process state (e.g. background or
cached).</p>
<p>Aside from the new garbage collectors, ART also introduces a new bitmap-based
memory allocator called RosAlloc (runs of slots allocator). This new allocator
has sharded locking and outperforms DlMalloc by adding thread local buffers for
small allocation sizes.</p>
<p>Compared to Dalvik, the ART CMS garbage collection plan has a number of
improvements:</p>
<ul>
<li>The number of pauses is reduced from two to one compared to Dalvik.
Dalvik’s first pause, which did mostly root marking, is done concurrently in
ART by getting the threads to mark their own roots, then resume running right away.
<li>Similarly to Dalvik, the ART GC also has a pause before the sweeping process.
The key difference in this area is that some of the Dalvik phases during this
pause are done concurrently in ART. These phases include
<code>java.lang.ref.Reference</code> processing, system weak sweeping (e.g. jni
weak globals, etc.), re-marking non-thread roots, and card pre-cleaning. The
phases that are still done paused in ART are scanning the dirty cards and
re-marking the thread roots, which helps reduce the pause time.
<li>The last area where the ART GC improves over Dalvik is with increased GC
throughput enabled by the sticky CMS collector. Unlike normal generational GC,
sticky CMS is non-moving. Instead of having a dedicated region for young
objects, young objects are kept in an allocation stack, which is basically an
array of <code>java.lang.Object</code>. This avoids moving objects required to
maintain low pauses but has the disadvantage of having longer collections for
heaps with complex object graphs.
</ul>
<p>The other main area where the ART GC is different than Dalvik is the
introduction of moving garbage collectors. The goal of moving GCs is to
reduce memory usage of backgrounded apps through heap compaction. Currently,
the event that triggers heap compaction is ActivityManager process-state
changes. When an app goes to background, it notifies ART the process state is
no longer jank “perceptible.” This enables ART do things that cause long
application thread pauses, such as compaction and monitor deflation. The two
current moving GCs that are in use are homogeneous space compaction and
semi-space compaction.</p>
<ul>
<li>Semi-space compaction moves objects between two tightly packed bump pointer
spaces. This moving GC occurs on low-memory devices since it saves slightly
more memory than homogeneous space compaction. The additional savings come
mostly from having tightly packed objects, which avoid RosAlloc / DlMalloc
allocator accounting overhead. Since CMS is still used in the foreground and it
can’t collect from a bump pointer space, semi space requires another transition
when the app is foregrounded. This is not ideal since it can cause a noticeable pause.
<li>Homogenous space compaction works by copying from one RosAlloc space to another
RosAlloc space. This helps reduce memory usage by reducing heap fragmentation.
This is currently the default compaction mode for non-low-memory devices. The
main advantage that homogeneous space compaction has over semi-space compaction
is not needing a heap transition when the app goes back to foreground.
</ul>
<h2 id=gc_verification_and_performance_options>GC verification and performance options</h2>
<p>It is possible to change the GC type if you are an OEM. The process for doing
this involves modifying system properties through adb. Keep in mind that these
are only modifiable on non-user or rooted builds.</p>
<h3 id=changing_the_gc_type>Changing the GC type</h3>
<p>There are ways to change the GC plans that ART uses. The main way to change the
foreground GC plan is by changing the <code>dalvik.vm.gctype</code> property or
passing in an <code>-Xgc:</code> option. It is possible to pass in multiple GC
options separated by commas.</p>
<p>In order to derive the entire list of available <code>-Xgc</code> settings,
it is possible to type <code>adb shell dalvikvm -help</code> to print the
various runtime command-line options.</p>
<p>Here is one example that changes the GC to semi space and turns on pre-GC heap
verification:
<code>adb shell setprop dalvik.vm.gctype SS,preverify</code></p>
<ul>
<li><code>CMS</code>, which is also the default value, specifies the
concurrent mark sweep GC plan. This plan consists of running sticky
generational CMS, partial CMS, and full CMS. The allocator for this plan is
RosAlloc for movable objects and DlMalloc for non-movable objects.
<li><code>SS</code> specifies the semi space GC plan. This plan has two semi
spaces for movable objects and a DlMalloc space for non-movable objects. The
movable object allocator defaults to a shared bump pointer allocator which uses
atomic operations. However, if the <code>-XX:UseTLAB</code> flag is also passed
in, the allocator uses thread local bump pointer allocation.
<li><code>GSS</code> specifies the generational semi space plan. This plan is
very similar to the semi-space plan with the exception that older-lived objects
are promoted into a large RosAlloc space. This has the advantage of needing to
copy significantly fewer objects for typical use cases.
</ul>
<h3 id=verifying_the_heap>Verifying the heap</h3>
<p>Heap verification is probably the most useful GC option for debugging
GC-related errors or heap corruption. Enabling heap verification causes the GC
to check the correctness of the heap at a few points during the garbage
collection process. Heap verification shares the same options as the ones that
change the GC type. If enabled, heap verification verifies the roots and
ensures that reachable objects reference only other reachable objects. GC
verification is enabled by passing in the following -<code>Xgc</code> values:</p>
<ul>
<li>If enabled, <code>[no]preverify</code> performs heap verification before starting the GC.
<li>If enabled, <code>[no]presweepingverify</code> performs heap verification
before starting the garbage collector sweeping
process.
<li>If enabled, <code>[no]postverify</code> performs heap verification after
the GC has finished sweeping.
<li><code>[no]preverify_rosalloc</code>,
<code>[no]postsweepingverify_rosalloc</code>,
<code>[no]postverify_rosalloc</code> are also additional GC options that verify
only the state of RosAlloc’s internal accounting. The main things verified are
that magic values match expected constants, and free blocks of memory are all
registered in the <code>free_page_runs_</code> map.
</ul>
<h3 id=using_the_tlab_allocator_option>Using the TLAB allocator option</h3>
<p>Currently, the only option that changes the allocator used without affecting
the active GC type is the TLAB option. This option is not available through
system properties but can be enabled by passing in -<code>XX:UseTLAB</code> to
<code>dalvikvm</code>. This option enables faster allocation by having a
shorter allocation code path. Since this option requires using either the SS or
GSS GC types, which have rather long pauses, it is not enabled by default.</p>
<h2 id=performance>Performance</h2>
<p>There are two main tools that can be used to measure GC performance. GC timing
dumps and systrace. The most visual way to measure GC performance problems
would be to use systrace to determine which GCs are causing long pauses or
preempting application threads. Although the ART GC is relatively efficient,
there are still a few ways to get performance problems through excessive
allocations or bad mutator behavior.</p>
<h3 id=ergonomics>Ergonomics</h3>
<p>Compared to Dalvik, ART has a few key differences regarding GC ergonomics. One
of the major improvements compared to Dalvik is no longer having GC for alloc
in cases where we start the concurrent GC later than needed. However, there is
one downside to this, not blocking on the GC can cause the heap to grow more
than Dalvik in some circumstances. Fortunately, ART has heap compaction, which
mitigates this issue by defragmenting the heap when the process changes to a
background process state.</p>
<p>The CMS GC ergonomics have two types of GCs that are regularly run. Ideally,
the GC ergonomics will run the generational sticky CMS more often than the
partial CMS. The GC does sticky CMS until the throughput (calculated by bytes
freed / second of GC duration) of the last GC is less than the mean throughput
of partial CMS. When this occurs, the ergonomics plan the next concurrent GC to
be a partial CMS instead of sticky CMS. After the partial CMS completes, the
ergonomics changes the next GC back to sticky CMS. One key factor that makes
the ergonomics work is that sticky CMS doesn’t adjust the heap footprint limit
after it completes. This causes sticky CMS to happen more and more often until
the throughput is lower than partial CMS, which ends up growing the heap.</p>
<h3 id=using_sigquit_to_obtain_gc_performance_info>Using SIGQUIT to obtain GC performance info</h3>
<p>It is possible to get GC performance timings for apps by sending SIGQUIT to
already running apps or passing in -<code>XX:DumpGCPerformanceOnShutdown</code>
to <code>dalvikvm</code> when starting a command line program. When an app gets
the ANR request signal (SIGQUIT) it dumps information related to its locks,
thread stacks, and GC performance.</p>
<p>The way to get GC timing dumps is to use:<br>
<code>$ adb shell kill -S QUIT <pid></code></p>
<p>This creates a <code>traces.txt</code> file in <code>/data/anr/</code>. This
file contains some ANR dumps as well as GC timings. You can locate the
GC timings by searching for: “Dumping cumulative Gc timings” These timings will
show a few things that may be of interest. It will show the histogram info for
each GC type’s phases and pauses. The pauses are usually more important to look
at. For example:</p>
<pre>
sticky concurrent mark sweep paused: Sum: 5.491ms 99% C.I. 1.464ms-2.133ms Avg: 1.830ms Max: 2.133ms
</pre>
<p><code>This</code> shows that the average pause was 1.83ms. This should be low enough to not
cause missed frames in most applications and shouldn’t be a concern.</p>
<p>Another area of interest is time to suspend. What time to suspend measures is
how long it takes a thread to reach a suspend point after the GC requests that
it suspends. This time is included in the GC pauses, so it is useful to
determine if long pauses are caused by the GC being slow or the thread
suspending slowly. Here is an example of what a normal time to suspend
resembles on a Nexus 5:</p>
<pre>
suspend all histogram: Sum: 1.513ms 99% C.I. 3us-546.560us Avg: 47.281us Max: 601us
</pre>
<p>There are also a few other areas of interest, such as total time spent, GC
throughput, etc. Examples:</p>
<pre>
Total time spent in GC: 502.251ms
Mean GC size throughput: 92MB/s
Mean GC object throughput: 1.54702e+06 objects/s
</pre>
<p>Here is an example of how to dump the GC timings of an already running app:
<pre>
$ adb shell kill -s QUIT &lt;pid&gt;
$ adb pull /data/anr/traces.txt
</pre>
<p>At this point the GC timings are inside of traces.txt. Here is example output
from Google maps:</p>
<pre>
Start Dumping histograms for 34 iterations for sticky concurrent mark sweep
ScanGrayAllocSpaceObjects: Sum: 196.174ms 99% C.I. 0.011ms-11.615ms Avg: 1.442ms Max: 14.091ms
FreeList: Sum: 140.457ms 99% C.I. 6us-1676.749us Avg: 128.505us Max: 9886us
MarkRootsCheckpoint: Sum: 110.687ms 99% C.I. 0.056ms-9.515ms Avg: 1.627ms Max: 10.280ms
SweepArray: Sum: 78.727ms 99% C.I. 0.121ms-11.780ms Avg: 2.315ms Max: 12.744ms
ProcessMarkStack: Sum: 77.825ms 99% C.I. 1.323us-9120us Avg: 576.481us Max: 10185us
(Paused)ScanGrayObjects: Sum: 32.538ms 99% C.I. 286us-3235.500us Avg: 986us Max: 3434us
AllocSpaceClearCards: Sum: 30.592ms 99% C.I. 10us-2249.999us Avg: 224.941us Max: 4765us
MarkConcurrentRoots: Sum: 30.245ms 99% C.I. 3us-3017.999us Avg: 444.779us Max: 3774us
ReMarkRoots: Sum: 13.144ms 99% C.I. 66us-712us Avg: 386.588us Max: 712us
ScanGrayImageSpaceObjects: Sum: 13.075ms 99% C.I. 29us-2538.999us Avg: 192.279us Max: 3080us
MarkingPhase: Sum: 9.743ms 99% C.I. 170us-518us Avg: 286.558us Max: 518us
SweepSystemWeaks: Sum: 8.046ms 99% C.I. 28us-479us Avg: 236.647us Max: 479us
MarkNonThreadRoots: Sum: 5.215ms 99% C.I. 31us-698.999us Avg: 76.691us Max: 703us
ImageModUnionClearCards: Sum: 2.708ms 99% C.I. 26us-92us Avg: 39.823us Max: 92us
ScanGrayZygoteSpaceObjects: Sum: 2.488ms 99% C.I. 19us-250.499us Avg: 37.696us Max: 295us
ResetStack: Sum: 2.226ms 99% C.I. 24us-449us Avg: 65.470us Max: 452us
ZygoteModUnionClearCards: Sum: 2.124ms 99% C.I. 18us-233.999us Avg: 32.181us Max: 291us
FinishPhase: Sum: 1.881ms 99% C.I. 31us-431.999us Avg: 55.323us Max: 466us
RevokeAllThreadLocalAllocationStacks: Sum: 1.749ms 99% C.I. 8us-349us Avg: 51.441us Max: 377us
EnqueueFinalizerReferences: Sum: 1.513ms 99% C.I. 3us-201us Avg: 44.500us Max: 201us
ProcessReferences: Sum: 438us 99% C.I. 3us-212us Avg: 12.882us Max: 212us
ProcessCards: Sum: 381us 99% C.I. 4us-17us Avg: 5.602us Max: 17us
PreCleanCards: Sum: 363us 99% C.I. 8us-17us Avg: 10.676us Max: 17us
ReclaimPhase: Sum: 357us 99% C.I. 7us-91.500us Avg: 10.500us Max: 93us
(Paused)PausePhase: Sum: 312us 99% C.I. 7us-15us Avg: 9.176us Max: 15us
SwapBitmaps: Sum: 166us 99% C.I. 4us-8us Avg: 4.882us Max: 8us
(Paused)ScanGrayAllocSpaceObjects: Sum: 126us 99% C.I. 14us-112us Avg: 63us Max: 112us
MarkRoots: Sum: 121us 99% C.I. 2us-7us Avg: 3.558us Max: 7us
(Paused)ScanGrayImageSpaceObjects: Sum: 68us 99% C.I. 68us-68us Avg: 68us Max: 68us
BindBitmaps: Sum: 50us 99% C.I. 1us-3us Avg: 1.470us Max: 3us
UnBindBitmaps: Sum: 49us 99% C.I. 1us-3us Avg: 1.441us Max: 3us
SwapStacks: Sum: 47us 99% C.I. 1us-3us Avg: 1.382us Max: 3us
RecordFree: Sum: 42us 99% C.I. 1us-3us Avg: 1.235us Max: 3us
ForwardSoftReferences: Sum: 37us 99% C.I. 1us-2us Avg: 1.121us Max: 2us
InitializePhase: Sum: 36us 99% C.I. 1us-2us Avg: 1.058us Max: 2us
FindDefaultSpaceBitmap: Sum: 32us 99% C.I. 250ns-1000ns Avg: 941ns Max: 1000ns
(Paused)ProcessMarkStack: Sum: 5us 99% C.I. 250ns-3000ns Avg: 147ns Max: 3000ns
PreSweepingGcVerification: Sum: 0 99% C.I. 0ns-0ns Avg: 0ns Max: 0ns
Done Dumping histograms
sticky concurrent mark sweep paused: Sum: 63.268ms 99% C.I. 0.308ms-8.405ms
Avg: 1.860ms Max: 8.883ms
sticky concurrent mark sweep total time: 763.787ms mean time: 22.464ms
sticky concurrent mark sweep freed: 1072342 objects with total size 75MB
sticky concurrent mark sweep throughput: 1.40543e+06/s / 98MB/s
Total time spent in GC: 4.805s
Mean GC size throughput: 18MB/s
Mean GC object throughput: 330899 objects/s
Total number of allocations 2015049
Total bytes allocated 177MB
Free memory 4MB
Free memory until GC 4MB
Free memory until OOME 425MB
Total memory 90MB
Max memory 512MB
Zygote space size 4MB
Total mutator paused time: 229.566ms
Total time waiting for GC to complete: 187.655us
</pre>
<h2 id=tools_for_analyzing_gc_correctness_problems>Tools for analyzing GC correctness problems</h2>
<p>There are various things that can cause crashes inside of ART. Crashes that
occur reading or writing to object fields may indicate heap corruption. If the
GC crashes when it is running, it could also point to heap corruption. There
are various things that can cause heap corruption, the most common cause is
probably incorrect app code. Fortunately, there are tools to debug GC and
heap-related crashes. These include the heap verification options specified
above, valgrind, and CheckJNI.</p>
<h3 id=checkjni>CheckJNI</h3>
<p>Another way to verify app behavior is to use CheckJNI. CheckJNI is a mode that
adds additional JNI checks; these aren’t enabled by default due to performance
reasons. The checks will catch a few errors that could cause heap corruption
such as using invalid/stale local and global references. Here is how to enable
CheckJNI:</p>
<pre>
$ adb shell setprop dalvik.vm.checkjni true
</pre>
<p>Forcecopy mode is another part of CheckJNI that is very useful for detecting
writes past the end of array regions. When enabled, forcecopy causes the array
access JNI functions to always return copies with red zones. A <em>red
zone</em> is a region at the end/start of the returned pointer that has a
special value, which is verified when the array is released. If the values in
the red zone don’t match what is expected, this usually means a buffer overrun
or underrun occurred. This would cause CheckJNI to abort. Here is how to enable
forcecopy mode:</p>
<pre>
$ adb shell setprop dalvik.vm.jniopts forcecopy
</pre>
<p>One example of an error that CheckJNI should catch is writing past the end of
an array obtained from <code>GetPrimitiveArrayCritical</code>. This operation
would very likely corrupt the Java heap. If the write is
within the CheckJNI red zone area, then CheckJNI would catch the issue when the
corresponding <code>ReleasePrimitiveArrayCritical</code> is called. Otherwise,
the write will end up corrupting some random object in
the Java heap and possibly causing a future GC crash. If the corrupted memory
is a reference field, then the GC may catch the error and print a “<em>Tried to
mark <ptr> not contained by any spaces</em>” error.</p>
<p>This error occurs when the GC attempts to mark an object for which it can’t
find a space. After this check fails, the GC traverses the roots and tries to
see if the invalid object is a root. From here, there are two options: The
object is a root or a non-root object.</p>
<h3 id=valgrind>Valgrind</h3>
<p>The ART heap supports optional valgrind instrumentation, which provides a
way to detect reads and writes to and from an invalid heap address. ART detects
when the app is running under valgrind and inserts red zones before and after
each object allocation. If there are any reads or writes to these red zones,
valgrind prints an error. One example of when this could happen is if you read
or write past the end of an array’s elements while using direct array access
through JNI. Since the AOT compilers use implicit null checks, it is
recommended to use eng builds for running valgrind. Another thing to note is
that valgrind is orders of magnitude slower than normal execution.</p>
<p>Here is an example use:</p>
<pre>
# build and install
$ mmm external/valgrind
$ adb remount && adb sync
# disable selinux
$ adb shell setenforce 0
$ adb shell setprop wrap.com.android.calculator2
"TMPDIR=/data/data/com.android.calculator2 logwrapper valgrind"
# push symbols
$ adb shell mkdir /data/local/symbols
$ adb push $OUT/symbols /data/local/symbols
$ adb logcat
</pre>
<h3 id=invalid_root_example>Invalid root example</h3>
<p>In the case where the object is actually an invalid root, it will print some
useful information:
<code>art E 5955 5955 art/runtime/gc/collector/mark_sweep.cc:383] Tried to mark 0x2
not contained by any spaces</code></p>
<pre>
art E 5955 5955 art/runtime/gc/collector/mark_sweep.cc:384] Attempting see if
it's a bad root
art E 5955 5955 art/runtime/gc/collector/mark_sweep.cc:485] Found invalid
root: 0x2
art E 5955 5955 art/runtime/gc/collector/mark_sweep.cc:486]
Type=RootJavaFrame thread_id=1 location=Visiting method 'java.lang.Object
com.google.gwt.corp.collections.JavaReadableJsArray.get(int)' at dex PC 0x0002
(native PC 0xf19609d9) vreg=1
</pre>
<p>In this case, <code>vreg 1</code> inside of
<code>com.google.gwt.corp.collections.JavaReadableJsArray.get</code> is
supposed to contain a heap reference but actually contains an invalid pointer
of address <code>0x2</code>. This is clearly an invalid root. The next step to
debug this issue would be to use <code>oatdump</code> on the oat file and look
at the method that has the invalid root. In this case, the error turned out to
be a compiler bug in the x86 backend. Here is the changelist that fixed it: <a
href="https://android-review.googlesource.com/#/c/133932/">https://android-review.googlesource.com/#/c/133932/</a></p>
<h3 id=corrupted_object_example>Corrupted object example</h3>
<p>In the case where the object isn’t a root, output similar to the following
prints:</p>
<pre>
01-15 12:38:00.196 1217 1238 E art : Attempting see if it's a bad root
01-15 12:38:00.196 1217 1238 F art :
art/runtime/gc/collector/mark_sweep.cc:381] Can't mark invalid object
</pre>
<p>When heap corruption isn’t an invalid root, it is unfortunately hard to debug.
This error message indicates that there was at least one object in the heap
that was pointing to the invalid object.</p>