Java StrictMode 活动实例计数违规(2 个实例,预期 1 个)在完全空的活动轮换时

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21145261/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 06:54:21  来源:igfitidea点击:

StrictMode activity instance count violation (2 instances, 1 expected) on rotation of completely empty activity

javaandroidmemory-leaksandroid-activity

提问by dhpiggott

Relevant only in that it motivates eliminating any false positives from strict mode, since the continued presence of any makes the death penalty impractical

相关的仅在于它促使从严格模式中消除任何误报,因为 any 的持续存在使得死刑不切实际

Over the last few days I've been chasing down and fixing memory leaks in an application. On getting to the point that I believed I'd fixed them all, I implemented a fail-loud mechanism similar to that described in Android StrictMode and heap dumps(enable instance tracking with death penalty, intercept the shutdown error message, dump heap, send a distress signal next time application starts). All just in debug builds of course.

在过去的几天里,我一直在追查和修复应用程序中的内存泄漏。到了我相信我已经解决了所有问题的地步时,我实现了一个类似于Android StrictMode 和堆转储中描述的失败大声机制(启用带有死刑的实例跟踪,拦截关闭错误消息,转储堆,发送下次申请开始时发出求救信号)。当然,所有这些都只是在调试版本中。

To the point

说到点子上了

Having believed I've fixed all activity leaks, certain activities still result in strict mode instance violation warnings on screen rotation. Curiously it is only some, not all of the application's activities that do this.

相信我已经修复了所有活动泄漏,某些活动仍然会导致屏幕旋转时出现严格模式实例违规警告。奇怪的是,这只是应用程序的一些活动,而不是所有活动。

I've reviewed heap dumps taken when such violations occur, and reviewed the code of the activities in question in search of the leak, but do not get anywhere.

我已经查看了发生此类违规时进行的堆转储,并查看了相关活动的代码以寻找泄漏,但一无所获。

So at this point I tried to make the smallest possible test case. I create a completely blank activity (no layout even), looking like this:

所以在这一点上,我试图制作尽可能小的测试用例。我创建了一个完全空白的活动(甚至没有布局),如下所示:

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);
    }

}

And for completeness, the manifest:

为了完整起见,清单:

<?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>

I open the activity (device held portrait). I rotate to landscape, and back to portrait, at which point I see from StrictMode this in LogCat:

我打开活动(设备保持纵向)。我旋转到横向,然后回到纵向,此时我在 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)

01-15 17:24:23.248:E/StrictMode(13867):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)

Heap dump

堆转储

At this point I manually acquire a heap dump using DDMS. Here are the two instances in MAT:

此时,我使用 DDMS 手动获取堆转储。以下是 MAT 中的两个实例:

1

1

And here's the one that was leaked, with the first part of the path from it to a GC root:

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

2

2

Note that no matter how many times I rotate between portrait and landscape, the number of actual instances never exceeds two, and the number of expected instances never exceeds one.

请注意,无论我在纵向和横向之间旋转多少次,实际实例的数量永远不会超过两个,预期实例的数量永远不会超过一个。

Can anyone make sense of the leak? Is it even a real leak? If it is, it presumably must be an Android bug. If it is not, the only other thing I can see that it could be is a bug in StrictMode.

任何人都可以理解泄漏吗?它甚至是真正的泄漏吗?如果是,那大概是安卓的bug。如果不是,我能看到的唯一其他可能是 StrictMode 中的错误。

Good answers might include

好的答案可能包括

  1. If this is a real leak, an explanation of how it occurs, and what if any action can be taken to fix it or suppress StrictMode from initiating the death penalty for such cases (recall that false positives/neutrals make the death penalty impractical).
  2. If this is not a real leak, an explanation of why StrictMode thinks otherwise, and what if any action can be taken to fix it or suppress StrictMode from initiating the death penalty for such cases (recall that false positives/neutrals make the death penalty impractical).
  3. In either case, hypotheses as to why this does not occur with all activities (the majority of activities in the application I'm working on do not produce such violations on rotation).
  1. 如果这是一个真正的泄漏,解释它是如何发生的,以及是否可以采取任何行动来修复它或抑制 StrictMode 对此类情况启动死刑(回想一下误报/中立使死刑不切实际)。
  2. 如果这不是真正的泄漏,解释为什么 StrictMode 不这么认为,以及是否可以采取任何行动来修复它或抑制 StrictMode 对此类情况启动死刑(回想一下误报/中立使死刑不切实际) )。
  3. 在任何一种情况下,关于为什么所有活动都不会发生这种情况的假设(我正在处理的应用程序中的大多数活动不会在轮换时产生此类违规)。

I've got as far as looking at the StrictMode source and see nothing obviously wrong there - as expected, it forces a GC before considering an extra instance to be a violation.

我已经查看了 StrictMode 源代码,并没有发现任何明显的错误 - 正如预期的那样,它在考虑一个额外的实例是违规之前强制执行 GC。

Good entry points for reading the StrictMode source are:

阅读 StrictMode 源代码的好切入点是:

  1. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.trackActivity%28java.lang.Object%29
  2. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.InstanceTracker.finalize%28%29
  3. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.incrementExpectedActivityCount%28java.lang.Class%29
  4. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.decrementExpectedActivityCount(java.lang.Class)
  1. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.trackActivity%28java.lang.Object%29
  2. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.InstanceTracker.finalize%28%29
  3. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.incrementExpectedActivityCount%28java.lang.Class%29
  4. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/StrictMode.java#StrictMode.decrementExpectedActivityCount(java.lang.Class)

Full disclosure

全面披露

I've only done these tests on one device, a Galaxy S4 running CyanogenMod 11 snapshot M2 (http://download.cyanogenmod.org/get/jenkins/53833/cm-11-20140104-SNAPSHOT-M2-jfltexx.zip), but I can't imagine CyanogenMod would deviate from KitKat in terms of activity management.

我只在一个设备上完成了这些测试,一个运行 CyanogenMod 11 快照 M2 的 Galaxy S4(http://download.cyanogenmod.org/get/jenkins/53833/cm-11-20140104-SNAPSHOT-M2-jfltexx.zip) ,但我无法想象 CyanogenMod 在活动管理方面会偏离 KitKat。

Additional StrictMode sources:

其他 StrictMode 来源:

  1. Activity's instanceTracker instance is just a final instance field: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/Activity.java#Activity.0mInstanceTracker
  2. Where expected activity instance counts are incremented: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#2095
  3. Where expected activity instance counts are decremented: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#3485
  1. Activity 的 instanceTracker 实例只是一个最终的实例字段:http: //grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/Activity.java#Activity .0mInstanceTracker
  2. 预期活动实例计数增加的地方:http: //grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#2095
  3. 预期活动实例数减少的地方:http: //grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#3485

采纳答案by matiash

I go with door #2: This is not a real leak.

我选择 2 号门:这不是真正的泄漏。

More specifically, it's just related to garbage collection. Three clues:

更具体地说,它只与垃圾收集有关。三个线索:

  1. The path to GC root ends at FinalizerReference, which is closely related to GC. It basically handles calling the finalize()methods on objects that are eligible for GC -- for which there is one here, namely the ViewRootImpl.WindowInputEventReceiverinstance, which extends InputEventReceiver, which does have a finalize()method. If this was a "real" memory leak, then the object would notbe eligible for GC, and there should be at least one other path to a GC root.

  2. At least in my test case, if I force GC beforetaking the heap snapshot, then there is only onereference to MainActivity(whereas there are two if I take the snapshot without doing so). Looks like forcing GC from DDMS actually includes calling all finalizers (most likely by calling FinalizerReference.finalizeAllEnqueued()which should release all these references.

  3. I could reproduce it in a device with Android 4.4.4, but not in Android L which has a new GC algorithm (admittedly this is at most circumstantial evidence, but it's consistent with the others).

  1. GC 根的路径结束于FinalizerReference,这与 GC 密切相关。它基本上处理finalize()在符合 GC 条件的对象上调用方法——这里有一个,即ViewRootImpl.WindowInputEventReceiver实例,它 extends InputEventReceiver,它确实有一个finalize()方法。如果这是一个“真正的”内存泄漏,那么该对象将符合 GC 的条件,并且至少应该有另一个通往 GC 根的路径。

  2. 至少在我的测试案例中,如果我拍摄堆快照之前强制执行 GC ,那么只有一个引用MainActivity(而如果我在不这样做的情况下拍摄快照则有两个)。看起来从 DDMS 强制 GC 实际上包括调用所有终结器(最有可能通过调用FinalizerReference.finalizeAllEnqueued()which 应该释放所有这些引用。

  3. 我可以在装有 Android 4.4.4 的设备中重现它,但不能在具有新 GC 算法的 Android L 中重现它(诚然,这最多是间接证据,但与其他人一致)。

Why does this happen for some activities and not for others? While I cannot say for sure, it's likely that constructing a "more complicated" activity fires GC (simply because it needs to allocate more memory) while a simple one like this one "generally" does not. But this should be variable.

为什么某些活动会发生这种情况,而其他活动不会?虽然我不能肯定地说,构建一个“更复杂”的活动很可能会触发 GC(仅仅是因为它需要分配更多的内存),而像这样的简单活动“通常”不会。但这应该是可变的。

Why does StrictMode think otherwise?

为什么 StrictMode 不这么认为?

There are extensive comments in StrictModeabout this case, check the source code of decrementExpectedActivityCount(). Nevertheless, it looks like it's not working exactly as they intended.

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);
    }

Update

更新

Actually, I've performed more tests, calling StrictMode.incrementExpectedActivityCount()using reflection, and I've found a very curious result, which does not change the answer (it's still #2) but I think provides an additional clue. If you increase the number of "expected" instances of the Activity (say, to 4), then the strict mode violation will still occur (claiming 5 instances are present), on every 4th rotation.

实际上,我已经执行了更多测试,StrictMode.incrementExpectedActivityCount()使用反射调用,我发现了一个非常奇怪的结果,它不会改变答案(它仍然是 #2),但我认为提供了一个额外的线索。如果您增加 Activity 的“预期”实例的数量(例如,增加到 4),那么每 4 次循环仍然会发生严格模式违规(声称存在 5 个实例)。

From this I'm led to conclude that the call to Runtime.getRuntime().gc()is what actually releases these finalizable objects, and that code runs only after going beyond the set limit.

由此我得出结论,调用Runtime.getRuntime().gc()实际上是释放这些可终结对象,并且该代码仅在超出设定限制后运行。

What if any action can be taken to fix it?

如果可以采取任何措施来修复它呢?

While it's not 100% foolproof, calling System.gc()in the Activity's onCreate()is likely to get this problem to go away (and it did in my tests). However, the specification for Java clearly says that garbage collection cannot be forced(and this is merely a hint) so I'm not sure if I'd trust going with the death penalty, even with this "fix"...

虽然它不是 100% 万无一失,但调用System.gc()ActivityonCreate()很可能会让这个问题消失(在我的测试中确实如此)。但是,Java 规范明确指出不能强制进行垃圾收集(这只是一个提示)所以我不确定我是否相信死刑,即使有这个“修复”......

You could possibly combine it with manually increasing the limit for activity instance count by calling reflection. But it seems like a really crude hack:

您可以将其与通过调用反射手动增加活动实例计数的限制相结合。但这似乎是一个非常粗略的黑客:

Method m = StrictMode.class.getMethod("incrementExpectedActivityCount", Class.class);
m.invoke(null, MainActivity.class);

(Note: be sure to do this only once at application startup).

(注意:确保在应用程序启动时只执行一次)。

回答by user467257

I am not an Android developer so this is a bit of a shot in the dark.

我不是 Android 开发人员,所以这有点像在黑暗中拍摄。

Presumably since the behaviour is not consistent for other activities, but is triggered nonetheless by a completely trivial one, the issue is down to the time taken for onCreate to complete.

据推测,由于其他活动的行为不一致,但仍然由一个完全微不足道的活动触发,问题归结为完成 onCreate 所需的时间。

If onCreate can complete before the pause/stop/destroy sequence is finished (and eventual garbage collection though it need not get quite to that point afaics) for the old activity then there really will be two instances present.

如果 onCreate 可以在暂停/停止/销毁序列完成之前完成(以及最终的垃圾收集,尽管它不需要完全达到那个点 afaics),那么实际上将存在两个实例。

A simple solution to prevent this is for the activity to have a static atomicboolean "ready" (initially false) that check before doing anything else in onCreate where you'd loop

防止这种情况的一个简单解决方案是让活动有一个静态 atomicboolean“准备好”(最初为假),在你将循环的 onCreate 中执行任何其他操作之前进行检查

while(!ready.compareAndSet(false, true)) {
    //sleep a bit
}

then override the ondestroy lifecycle callback and invoke ready.compareAndSet(true, false)

然后覆盖 ondestroy 生命周期回调并调用 ready.compareAndSet(true, false)

Apologies if this approach is utterly naive.

如果这种方法非常幼稚,请见谅。

回答by Maxim Kornilov

I want to summarize and provide the final answer to safe time for other developers.

我想总结并为其他开发人员提供安全时间的最终答案。

Is android.os.StrictMode$InstanceCountViolation a problem or not

android.os.StrictMode$InstanceCountViolation 是否有问题

This can be a real problem. To identify if this is a problem I can recommend the following post: Detecting leaked Activities in Android. If you will see that there are objects holding a reference to this activity which don't related to Android Framework then you have a problem which should be fixed by you.

这可能是一个真正的问题。为了确定这是否是一个问题,我可以推荐以下帖子:检测 Android 中泄露的活动。如果您会看到有对象持有对此活动的引用,而这些对象与 Android 框架无关,那么您就遇到了应该由您解决的问题。

In case there are no objects holding a reference to this activity which don't related to Android Framework than it means that you encountered with the problem related to how detectActivityLeaks check is implemented. Also as was pointed the garbage collection should help in this case

如果没有对象持有与 Android 框架无关的对此活动的引用,则意味着您遇到了与如何实现 detectActivityLeaks 检查相关的问题。同样正如所指出的,垃圾收集在这种情况下应该有所帮助

Why detectActivityLeaks works incorrectly and reproduces not on all devices

为什么detectActivityLeaks 工作不正常并且不能在所有设备上重现

If we will look at sources of the detectActivityLeaks:

如果我们查看detectActivityLeaks的来源:

// 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);
}

The problem here is that to count instances correctly, all previous ones should be garbage collected (at least logic is relay on this). And this is the main problem of this implementation. The reason why is that one round trip of the GC can't be enough in Java if you want to be sure that all objects which are ready to be released are collected by GC. In most cases it is enough to call System.gc()twice or use something similar to methods implemented in this jlibslibrary.

这里的问题是要正确计算实例,所有以前的实例都应该被垃圾收集(至少逻辑是中继)。这是这个实现的主要问题。原因是如果你想确保所有准备释放的对象都被 GC 收集,那么在 Java 中,GC 的一次往返是不够的。在大多数情况下,调用System.gc()两次或使用类似于此jlibs库中实现的方法就足够了。

That's why this issue reproduces on some devices (OS versions) and doesn't reproduce on another ones. This all depends on how GC is implemented and sometimes only one call is enough to be sure that object will be collected by GC.

这就是为什么此问题会在某些设备(操作系统版本)上重现而在其他设备上不会重现的原因。这一切都取决于 GC 是如何实现的,有时只需一次调用就足以确保 GC 收集对象。

How to avoid this issue in case there is no leaked ActivitiesI simply run System.gc()before starting activity in debug configuration like in the following example. This will help to avoid this problem and gives you ability to continue using detectActivityLeaks check.

如何在没有泄漏活动的情况下避免此问题我只是在调试配置中启动活动之前运行System.gc(),如下例所示。这将有助于避免此问题,并使您能够继续使用 detectActivityLeaks 检查。

 if (BuildConfig.DEBUG)
 {         
     System.gc();
 }
 Intent intent = new Intent(context, SomeActivity.class);
 this.startActivity(intent);

This also ensures that in release build Garbage Collection is not forced as this is not recommended.

这也确保在发布版本中不会强制执行垃圾收集,因为不推荐这样做。