Flutter“计算”内存泄漏-如何停用计算实例使用的堆变量?

时间:2019-04-15 18:20:09

标签: dart flutter

在将一堆下载的JSON反序列化为对象时,我试图在移动应用程序中使用compute实例来减少jank

当我使用compute实现反序列化方法时,堆会无限期地同时保留传递的JSON 返回的反序列化对象(在列表中)。即使该方法已关闭并且父/调用对象已退出,GC仍会正常触发但不会从堆中删除对象。因此,在使用DevTools内存分析器时,它显示出内存消耗失控-堆不断增大。

Normal memory profile正常内存配置文件-直接调用反序列化方法时,内存使用量徘徊在45MB左右(但这会导致应用中出现垃圾)

Runaway memory profile失控的内存配置文件-内存使用量呈线性增加,并且在通过计算调用反序列化方法(但不会在应用程序中造成垃圾)时永远不会停用

static Stream<EventCommitInfoModel> getEventsAfterDate(DateTime date) async* {

    // variable defs for scope reuse

    while (count < maxCount && retryCount > 0) {
      try {
        json = await http.read(url);

        // currentEvents = await compute(EventModel.fromJsonArray, json);
        currentEvents = EventModel.fromJsonArray(json);

        db = await AppStateModel.database;
        await db.upsertEventModels(currentEvents);
        yield new InfoModel(maxCount, currentEvents.length);
      }
      catch (ex) {

        // try again or close

      }
    }

    print("stream is closing.");
  }

在上面的代码中,相关行以“ currentEvents =”开头。正常的内存行为可以通过以下方式看到:

  currentEvents = EventModel.fromJsonArray(json);

并显示出失控的内存行为:

  currentEvents = await compute(EventModel.fromJsonArray, json);

请注意,将EventModel.fromJsonArray更改为异步方法对以上任何概要分析都具有 NO 的影响。将其更改为异步也不会导致垃圾消失。我已经考虑过了。我可以在代码中添加人为延迟,以便在映射方法中引入异步拆分,但这不是我要在这里做的-我需要数据尽快返回,这就是为什么使用计算机是理想的选择。

即使在打印“关闭流”并且关闭流,并且从层次结构撤消父对象并收集父对象之后,也永远不会撤消与计算方法关联的任何内存。

如何使计算实例正确地退出内存?我在这里做错什么了吗?

4 个答案:

答案 0 :(得分:2)

没有更多代码,似乎正在发生的事情是您正在创建的流从未真正关闭过。因此,流保持打开状态,并因此钩住了GC无法删除的json对象。

您在问题陈述中暗示流已关闭:

Even after "stream is closing" is printed, and the stream is closed, and ...

...但是,有关流将关闭的打印语句不会关闭流。仅仅到达代码块的末尾也不会关闭流(尤其是对于广播流),至少不会关闭。

根据Dart文档,他们也不建议直接对流进行操作; https://dart.dev/articles/libraries/creating-streams#final-hints也正是出于这个原因。他们建议使用流控制器。

答案 1 :(得分:0)

使用隔离时,回调参数必须是顶级函数。

  

回调参数必须是顶级函数,而不是闭包或   类的实例或静态方法。

您可以尝试以下操作:

class EventsRepo  {
  static const URL = 'https://some-json.com/events';

  @override
  Future<List<Event>> fetchEvents(http.Client client) async {
    final response = await client.get(URL);
    return compute(parseJson, response.body);
  }
}

// Top level function
List<Event> parseJson(String responseBody) {
  final parsed = json.decode(responseBody);
  return parsed.map<Event>((json) => EventModel.fromJson(json)).toList();
}

请注意顶层功能,希望有帮助。

答案 2 :(得分:0)

不确定Flutter的旧版本,但是Channel master, 1.21.0-6.0.pre.41的当前版本对compute函数没有任何问题。

我正在项目中使用它,似乎内存没有增长。

enter image description here

要点compute是杀死引擎盖下的孤立者。隔离在被杀死时释放它的记忆。

/// The dart:io implementation of [isolate.compute].
Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {

  ... other code ...

  resultPort.close();
  errorPort.close();
  isolate.kill();
  Timeline.finishSync();
  return result.future;
}

如果直接使用Isolate.spawn,请确保保留实例并在工作完成后手动将其杀死。我也正在使用这种方法。

release() {
  _subscription.cancel();
  _controller.close();
  _isolate.kill(
    priority: Isolate.immediate,
  );
}

答案 3 :(得分:0)

我在 flutter 2.2.3 上看到了同样的问题

我作为参数传递给计算的所有 data 都保存在内存中 - await compute(exampleGlobalFunction, data)

我在 Flutter DevTools / Memory 选项卡中检查过,数据实例从未发布。如果我直接调用函数(没有计算),那么数据会被正确释放。

编辑:我想我找到了解决方案 - 创建了一个 github 问题 https://github.com/flutter/flutter/issues/86470