dhp*_*ott 34 java android memory-leaks android-activity
在过去的几天里,我一直在追逐并修复应用程序中的内存泄漏.在达到我认为已经修复它们的点之后,我实现了一个类似于Android StrictMode和堆转储中描述的失败响应机制(启用实例跟踪并判处死刑,拦截关闭错误消息,转储堆,发送应用程序启动时的遇险信号).当然,所有这些只是在调试版本中.
相信我已经修复了所有活动泄漏,某些活动仍会导致屏幕旋转时出现严格的模式实例违规警告.奇怪的是,只有一些,而不是所有应用程序的活动都这样做.
我已经审查了发生此类违规时所采取的堆转储,并检查了相关活动的代码以查找泄漏,但没有得到任何结果.
所以在这一点上,我试图做出尽可能小的测试用例.我创建了一个完全空白的活动(甚至没有布局),看起来像这样:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
为了完整,清单:
<?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>
Run Code Online (Sandbox Code Playgroud)
我打开活动(设备持有肖像).我旋转到横向,然后回到肖像,此时我在LogCat中从StrictMode看到:
01-15 17:24:23.248:E/StrictMode(13867):class com.example.app.MainActivity; 实例= 2; limit = 1 01-15 17:24:23.248:E/StrictMode(13867):android.os.StrictMode $ InstanceCountViolation:class com.example.app.MainActivity; 实例= 2; limit = 1 01-15 17:24:23.248:E/StrictMode(13867):在android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)
此时,我使用DDMS手动获取堆转储.以下是MAT中的两个实例:

这是泄漏的那个,从它到GC根路径的第一部分:

请注意,无论我在纵向和横向之间旋转多少次,实际实例的数量永远不会超过两个,并且预期实例的数量永远不会超过一个.
谁能理解泄漏?它甚至是真正的泄漏吗?如果是,它可能必须是一个Android bug.如果不是,我唯一可以看到它可能是StrictMode中的一个错误.
我已经看到了StrictMode源,并且没有看到任何明显错误 - 正如预期的那样,它会在考虑额外的实例是违规之前强制执行GC.
读取StrictMode源的好入口点是:
我只在一台设备上完成了这些测试,一台运行CyanogenMod 11快照M2的Galaxy S4(http://download.cyanogenmod.org/get/jenkins/53833/cm-11-20140104-SNAPSHOT-M2-jfltexx.zip) ,但我无法想象CyanogenMod在活动管理方面会偏离KitKat.
mat*_*ash 20
我带着#2门走:这不是真正的泄漏.
更具体地说,它只与垃圾收集有关.三条线索:
GC根的路径结束于FinalizerReference,与GC密切相关.它基本上处理调用finalize()符合GC条件的对象的方法 - 这里有一个,即ViewRootImpl.WindowInputEventReceiver扩展的实例,它InputEventReceiver有一个finalize()方法.如果这是一个"真正的"内存泄漏,那么该对象将不符合GC的条件,并且至少应该有一个到GC根目录的其他路径.
至少在我的测试用例中,如果我在获取堆快照之前强制执行GC ,那么只有一个引用MainActivity(如果我不执行快照,则有两个引用).看起来像从DDMS强制GC实际上包括调用所有终结器(最有可能通过调用FinalizerReference.finalizeAllEnqueued()哪个应该释放所有这些引用.
我可以在Android 4.4.4的设备中重现它,但不能在具有新GC算法的Android L中重现它(不可否认,这是最多的间接证据,但它与其他人一致).
为什么这会发生在一些活动而不是其他活动?虽然我不能肯定地说,构建一个"更复杂"的活动很可能会激活GC(仅仅因为它需要分配更多的内存),而像这样一个简单的"通常"不会.但这应该是可变的.
为什么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);
}
Run Code Online (Sandbox Code Playgroud)
更新
实际上,我已经进行了更多测试,StrictMode.incrementExpectedActivityCount()使用反射进行调用,我发现了一个非常好奇的结果,这并没有改变答案(它仍然是#2),但我认为提供了额外的线索.如果增加活动的"预期"实例的数量(例如,增加到4),则在每第4次轮换时仍将发生严格模式违规(声称存在5个实例).
从此我得出结论,调用Runtime.getRuntime().gc()是实际释放这些可终结对象的内容,并且该代码仅在超出设置限制后运行.
如果可以采取任何行动来修复它怎么办?
虽然它不是100%万无一失,但调用System.gc()Activity onCreate()可能会让这个问题消失(而且在我的测试中确实如此).然而,Java的规范明确指出不能强制进行垃圾收集(这只是一个暗示)所以我不确定我是否会相信死刑,即使这个"修复"......
您可以通过调用反射手动增加活动实例计数的限制来组合它.但它似乎是一个非常粗糙的黑客:
Method m = StrictMode.class.getMethod("incrementExpectedActivityCount", Class.class);
m.invoke(null, MainActivity.class);
Run Code Online (Sandbox Code Playgroud)
(注意:确保在应用程序启动时只执行一次).
| 归档时间: |
|
| 查看次数: |
9828 次 |
| 最近记录: |