【问题标题】:robolectric memory leak due to numeorus ReceiverDispatcher and Activity instances由于 ReceiverDispatcher 和 Activity 实例导致的 robolectric 内存泄漏
【发布时间】:2016-08-08 15:53:52
【问题描述】:

我正在为我公司的项目编写单元测试。最近,我们的 Jenkins 构建在单元测试 gradle 任务期间开始失败,出现OutOfMemory 异常(由超出GC overhead 限制或Java heap space 引起)。在我的本地机器上,单元测试成功,但有时甚至使用 3.5 GB 的内存。项目中有超过 1000 个测试,其中数百个创建 Activity 实例。

我做了一个堆转储(在大约 1000 次测试通过的那一刻),并使用 VisualVM 对其进行了检查。由于内存使用量和堆大小都在不断增加,我怀疑某处发生了内存泄漏。

我使用堆转储运行 Eclipse 内存分析器,#1 泄漏嫌疑人有很多 ReceiverDispatcher 实例,其中大约 255k(使用 > 500 MB 的内存)。此外,堆包含相同数量的 IntentReceiverLeaked 实例,略多 (258k) shadowBroadcastReceiver 实例,少两倍(约 127k)AccessibilityManagerServiceLockPatternUtils 实例。

这对我来说很奇怪,因为我发现只有一个 BroadcastReceiver 在项目中动态注册(在 MainActivity 中),并且它在 Activity 的 onDestroy() 方法中被正确取消注册。

由于转储中大约​​有 300 个 Activity 实例,我怀疑泄漏的 Activity 是导致泄漏的主要原因。此外,Eclipse Memory Analyzer 将ShadowContextImpl 列为#2 问题嫌疑人,其实例略多于活动(但使用了大约 500 MB 的内存)。

测试中的Activity是使用Robolectric.setupActivity()创建的,相关的生命周期方法在tearDown()上调用

//Android Annotations are in use
protected ActivityController<MainActivity_> activityController;
protected MainActivity_ mainActivity;

@Before
public void setUp() throws Exception {
    activityController = Robolectric.buildActivity(MainActivity_.class).setup();
    mainActivity = activityController.get();
}

@After
public void tearDown() throws Exception {
    try {
        if (activityController != null) {
            activityController.pause()
                    .stop()
                    .destroy();
        }
    } catch (Throwable th){}

    mainActivity = null;
    activityController = null;
}

对于 Fragment 相关的测试,Fragment 以常规方式创建并附加到 Activity(而不是使用 FragmentTestUtil)

public static void startFragment(Fragment fragment, FragmentActivity activity) {
    shadowOf(Looper.getMainLooper()).pause();
    FragmentManager fragmentManager = activity.getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(fragment, null);
    fragmentTransaction.commitAllowingStateLoss();
    shadowOf(Looper.getMainLooper()).runToEndOfTasks();
}

(...)    
Activity activity = Robolectric.setupActivity(MainActivity_.class);
MyFragment fragment = MyFragment_.builder().build();
startFragment(fragment, activity);
(...) 

这些泄漏的原因可能是什么(以及如何解决)?启动活动和片段的方式是否与泄漏有关?

【问题讨论】:

  • 嘿 Piotr,这是一个长期存在的问题 github.com/robolectric/robolectric/issues/1700。不幸的是,它还没有修复。我会在那里添加你的发现
  • 谢谢,我会告诉 Robolectric 团队这件事。希望对解决问题有所帮助。

标签: android memory-leaks robolectric


【解决方案1】:

对于它的价值 - 在尝试将我们的应用程序迁移到 RoboElectric 4.3 和 AndroidX 时,我一直在尝试修复 OOM 错误 - 在经历和重构 2000 个测试以确保它们完成并处理我可以观看的片段和活动之前VisualVM 中的 gradle runner 进程最大可用堆,并且测试运行速度越来越慢,直到它们停止或因 OOM 错误而崩溃

这让我重新开始工作,将其添加到应用 build.grade:

 testOptions {
        unitTests {
            all {
                maxParallelForks = Runtime.runtime.availableProcessors()
                forkEvery = 100
            }
        }
}

意味着只有 100 个测试将在单个进程中运行,因此它们永远不会达到堆大小。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-29
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-09
    • 2016-07-10
    • 1970-01-01
    相关资源
    最近更新 更多