| 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 other 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 <pid> |
| $ 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> |