Java 为什么在Android Studio的(内存)档案器中FinalizerReference类的保留堆大小如此之大?

Java 为什么在Android Studio的(内存)档案器中FinalizerReference类的保留堆大小如此之大?,java,android,profiler,finalize,Java,Android,Profiler,Finalize,我读过。它可以追溯到2011年,当时工具不同,Java类仍然有不同的名称(Finalizer vs FinalizerReference)。因此,我认为现在可以提出这个类似但新的问题 除此之外,这个问题的公认答案归结为:避免使用finalize()d对象。直接或间接使用finalize()的Android类包括Canvas、Paint、Bitmap、Drawable和RenderNode。祝你好运,一直避开他们 我也读了这本书,并通读了这本书 后者将“保留大小”定义为“该类的所有实例占主导地位的

我读过。它可以追溯到2011年,当时工具不同,Java类仍然有不同的名称(Finalizer vs FinalizerReference)。因此,我认为现在可以提出这个类似但新的问题

除此之外,这个问题的公认答案归结为:避免使用finalize()d对象。直接或间接使用finalize()的Android类包括Canvas、Paint、Bitmap、Drawable和RenderNode。祝你好运,一直避开他们

我也读了这本书,并通读了这本书

后者将“保留大小”定义为“该类的所有实例占主导地位的内存大小”

问题是:我在上运行了探查器(顺便说一句,它被设计破坏了)。我将添加的文本视图数量限制在2000个,并且只点击了一次设备上的浮动操作按钮。在转储堆时,探查器报告FinalizerReference的保留大小是测试设备上可用内存的两倍。显然,某些占支配地位的记忆不止一次被计算在内


当然,我对自己程序的堆使用非常感兴趣。当探查器在堆的顶部显示FinalizerReference时,它似乎有误导性,它控制着可用的每个字节以及更多字节。我是否应该忽略FinalizerReference的保留堆大小?为什么我应该信任为其他类提供的值?

探查器似乎像计算任何其他类的FinalizerReference的保留堆大小一样。这是一个错误,因为FinalizerReference对于垃圾收集的反射性是独一无二的(下面将详细介绍)

一个简单的例子将说明这导致的荒谬结果。它还将明确为什么FinalizerReference似乎可以支配比系统中可用内存更多的内存

在androidstudio中,配置一个类似的应用程序并转储堆。(您可能需要触发垃圾收集并在转储之前等待其完成,以获得下面的结果。)切换到zygote堆。在堆转储窗格中查找FinalizerReference,可能位于顶部。查找列分配中列出的实例数,例如n。计算n*(n+1)/2*36。它是否等于列保留大小下的数字?我想是的

为什么这个公式有效?你自己看看吧。在堆转储窗格上选择FinalizerReference。在实例视图中向下滚动实例列表。根据需要经常选择“单击以查看下一个100”以进入列表底部。选择最后一个实例。请注意,在下面的窗格中,引用它的其他FinalizerReference中有一个“next”字段,但没有“prev”字段。还要注意,在这个例子中,浅表大小和保留大小是相同的,即36字节。然后查看列表上的保留大小顺序:36、72、108、144。。。现在将所有n个实例的值相加

上面给出的公式(简单地)不适用于应用程序堆,原因有二。一个是堆有时包含FinalizerReference实例,这些实例已从链表中取出,但尚未被垃圾收集。可以通过查看它们的内容来识别它们,其中显示了一个null referent,next和prev也为null。另一种情况是,应用程序实例列表底部的项目由合子实例列表顶部的项目引用。因此,应用程序堆上FinalizerReference(据称)的保留大小只能通过同时考虑zygote堆上的实例并排除所有未链接实例来计算

事情是这样的。FinalizerReference不是一个普通类。它是垃圾收集器在垃圾收集期间使用的类。这种自反性很重要。FinalizerReference实例的垃圾收集仅由垃圾收集触发

创建时,FinalizerReference实例将成为双链接列表的一部分,因此可以在不破坏列表的情况下删除任何位置的实例。但这也意味着大多数实例都保留对另外两个实例的引用。但是,唯一可以删除这些引用的操作是垃圾收集。垃圾收集器将查找除FinalizerReference实例以外的所有引用对象,运行其finalize()方法,对其进行垃圾收集,并从列表中删除引用它的FinalizerReference实例,从而依次对该实例进行垃圾收集

探查器目前所做的是将FinalizerReference的“第一个”实例计算为具有36字节的保留大小,等于其较浅的大小。对于第二个实例,它计算自己的36个浅字节,加上它有引用的第一个实例的36字节保留大小。对于第三个实例,它计算自己的36个浅字节,加上前两个实例的72+36保留大小。因此,当我们计算到数字100时,第一个实例的内存被计算了100次,第二个实例的内存被计算了99次,等等。这是没有意义的,除了一个“内存控制”的递归定义(在这个类中是误导性的和无意义的)

对于开发人员来说,FinalizerReference实例的有趣之处不是它引用的自己的类的其他实例,而是它的引用对象,特别是当该引用对象没有其他引用时。如果探查器对该类有用,它会将FinalizerReference类的保留大小计算为仅由FinalizerReferences实例引用的引用所占用的内存之和。这永远不会超过系统中的实际内存,任何过大的值都会通知开发人员一个问题,即对象创建和丢弃的速度比垃圾收集的速度快

就目前的情况而言,探查器只确认连续数据求和的数学公式