Status Update
Comments
sw...@gmail.com <sw...@gmail.com> #2
The issue reported here is independent of those problems, and I used the app only because it easily leads to absurd results.
uc...@google.com <uc...@google.com>
sh...@google.com <sh...@google.com> #3
The "retained size" calculation in the memory profiler is actually a bit different from what's described in
Instead of claiming all its references as belonging to its "retained" size, an object(A) is only deemed to be dominating/retaining another object (B)'s memory if B is no longer reachable from any GC roots if A is gone. That's how the algorithm should work anyway... there might be bugs. But in the case of a doubly-linked list, no element should dominate another.
I tried the codelab app you mentioned and the finalizer references appear to behave normally (see screenshots). I am curious how you got into the problematic state.. it's definitely not good. What device and android version are you using?
There might be a program state that I am not hitting when I am taking the heap dump. Would you mind sharing the hprof you have as well? Thanks again.
sw...@gmail.com <sw...@gmail.com> #4
I am surprised (and delighted) to see your screenshots. This is what I expected, but not what I am seeing on my side. I do notice that you have a column (Native Size) that is absent from my display, so that may be a clue.
My test device is a Samsung S4 (GT-I9500) with Android API 21.
I replicated the bug again, and attach the resulting hprof file. Please let me know if you need anything else.
sh...@google.com <sh...@google.com> #5
The native size column is only displayed in more recent Android versions. Certain classes of objects (e.g. String, Bitmap, among others) have moved to storing the data buffers in native, and that column is a workaround for us to account for those buffer memory.
It shouldn't be related to this issue though. I will take a look at your hprof and report back.
sw...@gmail.com <sw...@gmail.com> #6
Screenshot (showing Native Size now) and hprof attached.
sh...@google.com <sh...@google.com>
ph...@google.com <ph...@google.com> #7
Here's what I found so far for this bug: Although we have an accurate calculation of each instance's retained size, in the ClassifierSet
, we're just naively summing up the retained sizes of each instance and considering that the ClassifierSet
's retained size, overlooking that one instance's retained size may subsume another. As a result, we're displaying accurate retained sizes for instances, but not for classes.
The following is a snippet to reproduce this problem. In the heap dump for this program, each instance's retained size is correct (each list subsumes its sublist), but for the class's retained size, we're wrongly summing them all up. We're currently showing that class Chain
retains 60GB+, which is apparently bogus.
class Chain(val next: Chain?)
tailrec fun chain(n: Int, acc: Chain? = null): Chain? = when {
n < 0 -> acc
else -> chain(n - 1, Chain(acc))
}
private val chain = chain(100000)
sh...@google.com <sh...@google.com>
ph...@google.com <ph...@google.com> #8
This has been fixed by change Id79f94ccf3dcaed0edf8a944227190ed0e4a6dd4, which should be available since Electric Eel canary 5.
Description
One of the attached screen shots (App heap 85 GB) shows the Memory Profiler reporting about 85 GB of Retained Size for the FinalizerReference class, and a little more for the app heap. The test device has 2 GB of memory *in total*.
Even if the algorithm for calculating Retained Size could be defended, this result is misleading. When the value is not as obviously ludicrous as here, the developer may erroneously conclude that there is a memory leak.
I get it that different classes can dominate the same memory, so that entries in the Retained Size column cannot be simply summed. But when a single class dominates the same memory repeatedly, there is a problem.
Steps to reproduce:
1. Launch the Profiler with the Codelabs Memory Overload app <
2. Tap the floating action button on the device once and wait for the memory allocation to stabilize.
3. Select the Memory Profiler and dump the heap.
4. Look at the details of the app heap on the Heap Dump pane.
The other attached screen shot (Zygote heap 26 MB) shows the Retained Size for FinalizerReference on the zygote heap for the same Memory Overload app as about 26 MB. This may not seem unreasonably large, but the following calculation shows where it comes from, and it does not make sense:
1. Under the column Allocations, it shows 1,249 instances.
2. Calculate 1,249 * 1,250 / 2 * 36 = 28,102,500.
3. The corresponding Retained Size: 28,102,500.
4. It means that the 36 bytes of Shallow Size for each FinalizerReference instance is counted recursively when traversing the (doubly) linked list of instances.
The consecutive integer sum formula n * (n+1) / 2 * 36 does not work for the app heap, unless the instances on the zygote heap are taken into account, and instances with null referents are counted separately.
The memory dominated by FinalizerReference instances should not be calculated in the same way as for other classes. My best attempt at coming to terms with its unique status is given in this StackOverflow question and answer:
<
tldr: With FinalizerReference instances, Referents are more important than other instances of the same class.
Other questions on StackOverflow testify to the confusion created by this issue:
<
<
<