我们能强制触发Dart垃圾回收器吗?

6

我正在尝试模拟 Streams 的内存泄漏(仅用于教育目的),但如果垃圾回收器还没运行,我无法确定内存是否实际泄漏。

在纯 Dart 测试中,我可以强制进行 GC 扫描吗?

2个回答

7
你可以通过Dart DevTools Web GUI完成此操作。 https://dart.dev/tools/dart-devtools 使用更新的Dart版本,我们可以使用dart:developer API与Dart VM通信。这也是新的Dart DevTools从VM获取数据并执行操作(例如触发垃圾回收)的方式,您仍然可以通过进入Web界面手动执行此操作。
如果您想从代码本身触发此操作,可以使用vm_service包,该包使得从Dart VM服务协议交互变得容易。
一个示例可以在此处看到:
import 'dart:developer';
import 'dart:isolate';
import 'package:vm_service/vm_service_io.dart';

Future<void> main(List<String> args) async {
  final serverUri = (await Service.getInfo()).serverUri;

  if (serverUri == null) {
    print('Please run the application with the --observe parameter!');
    return;
  }

  final isolateId = Service.getIsolateID(Isolate.current)!;
  final vmService = await vmServiceConnectUri(_toWebSocket(serverUri));
  final profile = await vmService.getAllocationProfile(isolateId, gc: true);
  
  print(profile.memoryUsage?.heapUsage);
}

List<String> _cleanupPathSegments(Uri uri) {
  final pathSegments = <String>[];
  if (uri.pathSegments.isNotEmpty) {
    pathSegments.addAll(uri.pathSegments.where(
      (s) => s.isNotEmpty,
    ));
  }
  return pathSegments;
}

String _toWebSocket(Uri uri) {
  final pathSegments = _cleanupPathSegments(uri);
  pathSegments.add('ws');
  return uri.replace(scheme: 'ws', pathSegments: pathSegments).toString();
}

getAllocationProfile方法有一个可选参数gc,文档中说明如下:

如果提供了gc并将其设置为true,则在收集分配信息之前将尝试进行垃圾回收。不能保证实际执行垃圾回收。

所以这确实是你能做的最好的事情。此解决方案仅在使用--observe参数启动程序时才有效。因此,除了调试目的外,您不应该使用此功能。

这些链接都是404错误。 - shawnblais
啊,所以不能再从代码中调用了吗?有点遗憾。 - shawnblais
好像当使用 dart compile aot my.dart 编译时,我不能这样做?还有其他的方法吗?谢谢! - ch271828n
@ch271828n 不,当编译为AOT时不可能。此外,应注意在生产中永远不需要手动触发应用程序的GC。 - julemand101
2
@julemand101 谢谢!这个完美地运行了(我个人需要从测试中触发垃圾回收,以检查WeakReference清除是否被正确处理)。还要注意,在测试中不需要检查serverUri是否为空。 - f-person
显示剩余7条评论

1

对于那些想在 Dart/Flutter 测试中触发 GC(例如 dart test my_file.dart),以下是基于 @julemand101 的解决方案:

import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'dart:isolate';

import 'package:common_dart/utils/processes.dart';
import 'package:front_log/front_log.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart' hide Isolate, Log;
import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:vm_service/vm_service_io.dart';

const _kTag = 'vm_services';

FutureOr<void> runTestsInVmService(
  FutureOr<void> Function(VmServiceUtil) body, {
  required String selfFilePath,
}) async {
  Log.d(_kTag, 'runInVmService selfFilePath=$selfFilePath Platform.script.path=${Platform.script.path}');

  if (Platform.script.path == selfFilePath) {
    final vmService = await VmServiceUtil.create();
    tearDownAll(vmService.dispose);
    await body(vmService);
  } else {
    test('run all tests in subprocess', () async {
      await executeProcess('dart', ['run', '--enable-vm-service', selfFilePath]);
    });
  }
}

class VmServiceUtil {
  static const _kTag = 'VmServiceUtil';

  final VmService vmService;

  VmServiceUtil._(this.vmService);

  static Future<VmServiceUtil> create() async {
    final serverUri = (await Service.getInfo()).serverUri;
    if (serverUri == null) {
      throw Exception('Cannot find serverUri for VmService. '
          'Ensure you run like `dart run --enable-vm-service path/to/your/file.dart`');
    }

    final vmService = await vmServiceConnectUri(_toWebSocket(serverUri), log: _Log());
    return VmServiceUtil._(vmService);
  }

  void dispose() {
    vmService.dispose();
  }

  Future<void> gc() async {
    final isolateId = Service.getIsolateID(Isolate.current)!;
    final profile = await vmService.getAllocationProfile(isolateId, gc: true);
    Log.d(_kTag, 'gc triggered (heapUsage=${profile.memoryUsage?.heapUsage})');
  }
}

String _toWebSocket(Uri uri) {
  final pathSegments = [...uri.pathSegments.where((s) => s.isNotEmpty), 'ws'];
  return uri.replace(scheme: 'ws', pathSegments: pathSegments).toString();
}

class _Log extends vm_service.Log {
  static const _kTag = 'vm_services';

  @override
  void warning(String message) => Log.w(_kTag, message);

  @override
  void severe(String message) => Log.e(_kTag, message);
}


Future<void> executeProcess(String executable, List<String> arguments) async {
  Log.d(_kTag, 'executeProcess start `$executable ${arguments.join(" ")}`');

  final process = await Process.start(executable, arguments);

  process.stdout.listen((e) => Log.d(_kTag, String.fromCharCodes(e)));
  process.stderr.listen((e) => Log.d(_kTag, '[STDERR] ${String.fromCharCodes(e)}'));

//  stdout.addStream(process.stdout);
//  stderr.addStream(process.stderr);

  final exitCode = await process.exitCode;
  Log.d(_kTag, 'executeProcess end exitCode=$exitCode');
  if (exitCode != 0) {
    throw Exception('Process execution failed (exitCode=$exitCode)');
  }
}

如何使用它:

void main() {
  runTestsInVmService(
    _core,
    selfFilePath: 'path/to/my/file.dart',
  );
}

void _core(VmServiceUtil vmService) {
  test('hello', () async {
    do_something();
    await vmService.gc();
    do_another_thing();
  });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接