Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
339 views
in Technique[技术] by (71.8m points)

java - Why is the retained heap size of the FinalizerReference class so large in the (memory) Profiler of Android Studio?

I have read this question about Finalizer's lion share of the heap. It dates from 2011 when the tools were different and the Java class still had a different name (Finalizer vs FinalizerReference). So I think this similar but new question can be asked now.

On top of that, the accepted answer to that question boils down to: avoid using finalize()'d objects. Android classes that use finalize() directly or indirectly include Canvas, Paint, Bitmap, Drawable and RenderNode. Good luck avoiding all of them all of the time.

I have also read the Profiler documentation and worked through the Memory Profiler Codelab.

The latter defines "Retained Size" as "Size of memory that all instances of this class are dominating."

So here is the problem: I ran Profiler on the Codelabs Memory Overload app (which is broken by design, by the way). I limited the number of added TextViews to 2000, and tapped the floating action button on the device only once. On dumping the heap, Profiler reported the Retained Size of FinalizerReference to be double the memory available on my test device. Obviously some dominated memory is counted more than once.

Of course I am really interested in the heap use of my own programs. Profiler seems to be misleading when it shows FinalizerReference at the top of the heap, dominating every byte available and more. Should I ignore the retained heap size of FinalizerReference? Why should I trust the value given for other classes?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Profiler seems to calculate the Retained heap size for FinalizerReference like that of any other class. This is a mistake, as FinalizerReference is unique in its reflexivity with respect to garbage collection (more below).

A simple example will demonstrate the absurd result to which this leads. It will also make clear why FinalizerReference can seem to dominate more memory than is available in the system.

In Android Studio, Profile an app like Codelabs Memory Overload and dump the heap. (You may need to trigger garbage collection and wait for its completion before dumping, to get the result below.) Switch to the zygote heap. Find FinalizerReference on the Heap Dump pane, likely at the top. Find the number of instances listed in the column Allocations, say n. Calculate n*(n+1)/2*36. Does it equal the number under the column Retained Size? I thought so.

Why does this formula work? See for yourself. Select FinalizerReference on the Heap Dump pane. Scroll down the list of instances in the Instance View. Select "Click to see next 100" as often as necessary to get to the bottom of the list. Select the last instance. Note on the pane below that there is a "next" field inside some other FinalizerReference that references it, but no "prev" field. Also observe that for this instance, the Shallow and Retained sizes are the same, namely 36 bytes. Then look at the sequence of Retained sizes going up the list: 36, 72, 108, 144, ... Now add up those values for all the n instances.

The formula given above does not (simply) work for the app heap, for two reasons. One is that the heap sometimes contains instances of FinalizerReference that have been taken out of the linked list, but have not yet been garbage collected. They can be identified by looking at their contents, which shows a null referent, with next and prev also null. The other is that the item at the bottom of the app instance list is referred to by the item at the top of the zygote instance list. So the (purported) Retained size of FinalizerReference on the app heap can only be calculated by taking into account the instances on the zygote heap as well, and by excluding all unlinked instances.

Here is the thing. FinalizerReference is not an ordinary class. It is a class used by the garbage collector, during garbage collection. This reflexivity is important. The garbage collection of FinalizerReference instances is only triggered by garbage collection.

On creation, FinalizerReference instances are made part of a doubly linked list, so that an instance in any position can be removed without breaking the list. But this also means that most instances maintain references to two others. However, the only operation that can remove those references is a garbage collection. The garbage collector finds every object that is referenced by nothing but a FinalizerReference instance, runs its finalize() method, garbage collects it and removes the FinalizerReference instance that refers to it from the list, allowing that instance to be garbage collected in turn.

What Profiler does at the moment, is to count the "first" instance of FinalizerReference as having a Retained size of 36 bytes, equal to its Shallow size. For the second instance, it counts its own 36 Shallow bytes, plus the 36 byte Retained size of the first instance, to which it has a reference. For the third instance, it counts its own 36 Shallow bytes, plus the 72 + 36 Retained sizes of the previous two instances. So when we get to number 100, the memory of the first instance has been counted 100 times, that of the second instance 99 times, etc. This makes no sense, apart perhaps from a (misleading and meaningless, in the case of this class) recursive definition of "memory domination".

For the developer, the interesting thing about a FinalizerReference instance is not the other instances of its own class that it refers to, but its referent, especially if that referent has no other references to it. If Profiler would be useful for this class, it would calculate the Retained size of the FinalizerReference class as the sum of the memory occupied by the referents referred to only by FinalizerReferences instances. That would never be more than the actual memory in the system, and any inordinate value would inform the developer of a problem where objects are created and discarded faster than they can be garbage collected.

As things stand, Profiler only confirms the mathematical formula for summing consecutive integers (by traversing the FinalizerReference list, it seems, and actually adding up those numbers!). In this sense it is not wrong, but its interpretation of the result as the retained heap size of FinalizerReference is still an error. It is misleading, and it certainly does not help the developer to understand what is happening on the heap.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...