- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
在过去的几天里,我一直在追查和修复应用程序中的内存泄漏。到了我相信我已经修复了所有问题的地步,我实现了一个类似于 Android StrictMode and heap dumps 中描述的故障响亮机制。 (启用带有死刑的实例跟踪,拦截关闭错误消息,转储堆,下次应用程序启动时发送求救信号)。当然,所有这些都只是在调试版本中。
相信我已经修复了所有 Activity 泄漏,但某些 Activity 仍然会导致屏幕旋转时出现严格模式实例违规警告。奇怪的是,只有应用程序的一些 Activity ,而不是所有的 Activity 都会这样做。
我查看了在发生此类违规行为时进行的堆转储,并查看了相关 Activity 的代码以查找泄漏,但没有得到任何结果。
所以在这一点上,我尝试制作尽可能小的测试用例。我创建了一个完全空白的 Activity (甚至没有布局),如下所示:
package com.example.app;
import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
StrictMode.setVmPolicy(
new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
StrictMode.setThreadPolicy(
new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyDeath()
.penaltyLog()
.build());
super.onCreate(savedInstanceState);
}
}
为了完整起见, list :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.app.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
我打开 Activity (设备手持肖像)。我旋转到横向,然后又回到纵向,此时我在 LogCat 中从 StrictMode 看到这个:
01-15 17:24:23.248: E/StrictMode(13867): class com.example.app.MainActivity; instances=2; limit=1 01-15 17:24:23.248: E/StrictMode(13867): android.os.StrictMode$InstanceCountViolation: class com.example.app.MainActivity; instances=2; limit=1 01-15 17:24:23.248: E/StrictMode(13867): at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)
此时我使用 DDMS 手动获取堆转储。以下是 MAT 中的两个实例:
这是泄露的,从它到 GC 根的路径的第一部分:
请注意,无论我在纵向和横向之间旋转多少次,实际实例的数量永远不会超过两个,而预期的实例数量永远不会超过一个。
任何人都可以理解泄漏吗?它甚至是真正的泄漏吗?如果是这样,它可能一定是一个 Android 错误。如果不是,我能看到的唯一其他可能是 StrictMode 中的错误。
我已经查看了 StrictMode 源,并没有发现任何明显错误 - 正如预期的那样,它会在将额外实例视为违规之前强制执行 GC。
阅读 StrictMode 源代码的良好入口点是:
我只在一台设备上完成了这些测试,一台运行 CyanogenMod 11 快照 M2 (http://download.cyanogenmod.org/get/jenkins/53833/cm-11-20140104-SNAPSHOT-M2-jfltexx.zip) 的 Galaxy S4,但我无法想象 CyanogenMod 在 Activity 管理方面会偏离 KitKat。
最佳答案
我选择 2 号门:这不是真正的泄漏。
更具体地说,它只是与垃圾收集有关。三个线索:
GC 根路径结束于 FinalizerReference
,这与GC密切相关。它基本上处理对符合 GC 条件的对象调用 finalize()
方法——这里有一个,即 ViewRootImpl.WindowInputEventReceiver
实例,它扩展了 InputEventReceiver
,它确实有一个 finalize()
方法。如果这是“真正的”内存泄漏,那么该对象将符合 GC 条件,并且应该至少有一个其他路径到 GC 根。
至少在我的测试用例中,如果我 在 拍摄堆快照之前强制 GC,那么只有 一个 对 MainActivity
(而如果我不这样做拍摄快照,则有两个)。看起来从 DDMS 强制 GC 实际上包括调用所有终结器(很可能通过调用应该释放所有这些引用的
FinalizerReference.finalizeAllEnqueued()
。
我可以在装有 Android 4.4.4 的设备中重现它,但不能在具有新 GC 算法的 Android L 中重现(诚然,这最多只是间接证据,但与其他证据一致)。
为什么某些 Activity 会发生这种情况,而其他 Activity 则不会?虽然我不能肯定地说,构造一个“更复杂”的 Activity 很可能会触发 GC(仅仅是因为它需要分配更多的内存),而像这样的简单 Activity “通常”不会。但这应该是可变的。
为什么 StrictMode 不这样认为?
StrictMode
中有大量关于此案例的注释,请查看decrementExpectedActivityCount()
的源代码。然而,它看起来并没有完全按照他们的预期工作。
// Note: adding 1 here to give some breathing room during
// orientation changes. (shouldn't be necessary, though?)
limit = newExpected + 1;
...
// Quick check.
int actual = InstanceTracker.getInstanceCount(klass);
if (actual <= limit) {
return;
}
// Do a GC and explicit count to double-check.
// This is the work that we are trying to avoid by tracking the object instances
// explicity. Running an explicit GC can be expensive (80ms) and so can walking
// the heap to count instance (30ms). This extra work can make the system feel
// noticeably less responsive during orientation changes when activities are
// being restarted. Granted, it is only a problem when StrictMode is enabled
// but it is annoying.
Runtime.getRuntime().gc();
long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
Throwable tr = new InstanceCountViolation(klass, instances, limit);
onVmPolicyViolation(tr.getMessage(), tr);
}
更新
其实我已经进行了更多的测试,使用反射调用StrictMode.incrementExpectedActivityCount()
,我发现了一个非常奇怪的结果,它并没有改变答案(它仍然是#2)但我认为提供了一个额外的线索。如果您增加 Activity 的“预期”实例数(例如,增加到 4 个),那么仍然会发生严格模式违规(声称存在 5 个实例),每 4 次轮换一次。 p>
由此我得出结论,对 Runtime.getRuntime().gc()
的调用是实际释放这些可终结对象的原因,并且该代码仅在超出设置限制后才运行.
如果可以采取任何措施来修复它怎么办?
虽然它不是 100% 万无一失,但在 Activity 的 onCreate()
中调用 System.gc()
可能会解决这个问题(它确实在我的测试)。然而,Java 的规范清楚地表明垃圾收集不能被强制(这只是一个提示)所以我不确定我是否会相信死刑,即使有这个“修复”...
您可以将它与通过调用反射手动增加 Activity 实例计数的限制相结合。但这似乎是一个非常粗暴的 hack:
Method m = StrictMode.class.getMethod("incrementExpectedActivityCount", Class.class);
m.invoke(null, MainActivity.class);
(注意:确保只在应用程序启动时执行一次)。
关于java - StrictMode Activity 实例计数违规(2 个实例,1 个预期)在完全空 Activity 轮换时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21145261/
我是一名优秀的程序员,十分优秀!