Leak analysis failed - error suggested I report this failure to LeakCanary. Including a link to .hprof and the logcat below.
https://drive.google.com/open?id=1Eez5QchPJ9zjQqK-noO-qzh95tplHz82
```2019-08-12 14:53:02.159 16258-16289/com.radiocom.staging D/LeakCanary: Found 2 retained references, dumping the heap
2019-08-12 14:53:02.176 16258-16289/com.radiocom.staging I/adiocom.stagin: hprof: heap dump "/storage/emulated/0/Download/leakcanary-com.radiocom.staging/2019-08-12_14-53-02_168.hprof" starting...
2019-08-12 14:53:08.147 16258-16289/com.radiocom.staging D/LeakCanary: Checking retained object because found new object retained
2019-08-12 14:53:08.147 16258-16289/com.radiocom.staging D/LeakCanary: No retained objects
2019-08-12 14:53:08.253 16258-23141/com.radiocom.staging D/LeakCanary: Analysis in progress, working on: PARSING_HEAP_DUMP
2019-08-12 14:53:21.928 16258-23141/com.radiocom.staging D/LeakCanary: Analysis in progress, working on: REPORTING_HEAP_ANALYSIS
2019-08-12 14:53:21.931 16258-23141/com.radiocom.staging D/LeakCanary: HeapAnalysisFailure(heapDumpFile=/storage/emulated/0/Download/leakcanary-com.radiocom.staging/2019-08-12_14-53-02_168.hprof, createdAtTimeMillis=1565643201930, analysisDurationMillis=13678, exception=
java.lang.RuntimeException: Not enough memory to allocate buffers for rehashing: 2097152 -> 4194304
at shark.internal.hppc.LongObjectScatterMap.allocateBuffers(LongObjectScatterMap.kt:275)
at shark.internal.hppc.LongObjectScatterMap.allocateThenInsertThenRehash(LongObjectScatterMap.kt:307)
at shark.internal.hppc.LongObjectScatterMap.set(LongObjectScatterMap.kt:99)
at shark.internal.HprofInMemoryIndex$Builder.onHprofRecord(HprofInMemoryIndex.kt:170)
at shark.HprofReader.readHprofRecords(HprofReader.kt:424)
at shark.internal.HprofInMemoryIndex$Companion.createReadingHprof(HprofInMemoryIndex.kt:249)
at shark.internal.HprofInMemoryIndex$Companion.createReadingHprof$default(HprofInMemoryIndex.kt:211)
at shark.HprofHeapGraph$Companion.indexHprof(HprofHeapGraph.kt:187)
at shark.HeapAnalyzer.analyze(HeapAnalyzer.kt:100)
at leakcanary.internal.HeapAnalyzerService.onHandleIntentInForeground(HeapAnalyzerService.kt:61)
at leakcanary.internal.ForegroundService.onHandleIntent(ForegroundService.kt:55)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:76)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.os.HandlerThread.run(HandlerThread.java:65)
Caused by: java.lang.OutOfMemoryError: Failed to allocate a 33554456 byte allocation with 10849984 free bytes and 10MB until OOM, max allowed footprint 201326592, growth limit 201326592
at shark.internal.hppc.LongObjectScatterMap.allocateBuffers(LongObjectScatterMap.kt:269)
at shark.internal.hppc.LongObjectScatterMap.allocateThenInsertThenRehash(LongObjectScatterMap.kt:307)
at shark.internal.hppc.LongObjectScatterMap.set(LongObjectScatterMap.kt:99)
at shark.internal.HprofInMemoryIndex$Builder.onHprofRecord(HprofInMemoryIndex.kt:170)
at shark.HprofReader.readHprofRecords(HprofReader.kt:424)
at shark.internal.HprofInMemoryIndex$Companion.createReadingHprof(HprofInMemoryIndex.kt:249)
at shark.internal.HprofInMemoryIndex$Companion.createReadingHprof$default(HprofInMemoryIndex.kt:211)
at shark.HprofHeapGraph$Companion.indexHprof(HprofHeapGraph.kt:187)
at shark.HeapAnalyzer.analyze(HeapAnalyzer.kt:100)
at leakcanary.internal.HeapAnalyzerService.onHandleIntentInForeground(HeapAnalyzerService.kt:61)
at leakcanary.internal.ForegroundService.onHandleIntent(ForegroundService.kt:55)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:76)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.os.HandlerThread.run(HandlerThread.java:65)
)
```
Trying to trigger memory leaks within app with LeakCanary monitoring, LeakCanary had a HeapAnalysisFailure
Thanks!
Running the analysis with the profiler:
Generally, we have two directions to investigate: a) the baseline memory required post hprof indexing, and how to lower that and b) the spikes while analyzing. The baseline is the most impactful right now.
I dumped the heap post indexing:
So our baseline is 133Mb (makes sense that memory goes down to 140Mb, 7Mb of baseline java memory for instrumentation tests is reasonable) and the big question is: how can we reduce that, and what do we have to sacrifice for it?
There's ~2M instances and 8 bytes per ref so we should use 16Mb per array. Not sure why keys is double the size of values, long et object ref are the same size.
Edit: it's a 32 bit emulator so references use only 4 bits.
Which makes me think, might be worth storing those ids as ints when parsing a heap dump from a 32bit platform. That'd require maintaining parallel data structures (for the maps but also the sealed class that are values in the map), so not great.
A few more observations:
position to figure out where to read in the heap dump. So position is only useful when performing IO, as opposed to the rest of the index which is useful to skip reads. So we could store the "id=>position" map on disk. Would need to be efficient for the initial write phase and subsequence reads.The total cost of IndexedInstance (which we have 1.4M instances here) is 8 bit key + 4bit value ref + 32bit = 44bits per instance.
We could inline the index sealed classes and split our object index maps into one map per type of thing to store.
IndexedClass: long => (position, superclassId, instanceSize) => 32 bits per instance instead of 56
IndexedInstance: long => (position, classId) => 24 bits per instance instead of 44bits
Same for IndexedObjectArray
IndexedPrimitiveArray: long => (position, type) => 5 bits per instance instead of 44 bits
This would approximately divide by two the total memory used by the index in this case. The perf hit is that it's slower to find an object by id, but that should still be pretty good. Probably less than 4x (we'll always look up the bigger map first)
If we dump the position to a file index we can reduce this further by another 30%.
The hprof string cache can be reduced by storing a reversed trie as value instead of strings directly. Class names have a lot of repetitive prefix so this should compress well.
Thinking about this some more: we probably just need one new map type, long to n-bytes, where n is provided when building the map and each entry has n bytes allocated. The tricky part is writing and reading with minimal allocations, which probably requires exposing access to the underlying array.
Also, the size of position can be dynamic, based on the size of the hprof. In this example we'd need 4 bytes. We can store it as unsigned.
Answering some of my prior questions:
Confirmed that adding a map of thread names removes the spike and memory stays flat when enqueuing GC roots. Nice!
Skipping reading the dynamic content of arrays and classes and instance saves significant allocations and memory while reading (no impact on the baseline though)
Splitting the index in 4 (one per object type) and replacing the 3 largest indexes with nbyte maps goes from 120,816,776 to 82,604,520, a 32% reduction.
Another idea: the load factor is 0.75% which makes sense as the array is growing. However, once done, we might want to rehash to 100% and save the unused space.
@nowele-rechka-entercom btw, it looks like there are 161 leaks in this heap dump..
@nowele-rechka-entercom btw, it looks like there are 161 leaks in this heap dump..
Will we be able to get access to that report or should we wait to rerun with the LeakCanary update?
You can use Shark CLI directly against the hprof file: https://square.github.io/leakcanary/shark/#shark-cli