Skip to content

JVM / Dart 内存模型

JVM 与 Dart 的内存模型都在回答同一个根问题:对象放在哪里、谁可以访问、何时可见、何时回收、为什么会泄漏或卡顿。只是两者对共享内存与并发的取舍不同。

一句话定义

  • JVM:多线程共享堆内存,靠 JMM、锁、volatile、原子类去协调可见性与并发安全
  • Dart:默认以 isolate 为边界,每个 isolate 独立堆,不共享普通对象,从模型上减少共享内存竞态

为什么需要

内存模型不是“面试八股”,而是直接决定这些问题能不能说清:

  • 为什么 Android 会 OOM、泄漏、GC 卡顿
  • 为什么某个对象明明置空了还没释放
  • 为什么 Dart isolate 默认不共享对象
  • 为什么多线程共享缓存时要考虑可见性
  • 为什么 Flutter 页面销毁了,但某些对象还活着

底层机制

1. JVM:线程共享堆,栈保存线程私有执行现场

经典拆法:

  • 线程栈:局部变量、调用栈帧
  • :对象实例、数组
  • 类元数据区 / Metaspace:类信息、方法元数据、常量池等

直觉理解:

  • 局部变量大多在线程自己的栈里,天然线程私有
  • new 出来的对象通常在堆里,被多个线程共享访问的风险更高

这就是为什么:

  • 局部计算一般不用加锁
  • 全局缓存、单例、共享状态要考虑同步与可见性

2. Dart:每个 isolate 自己一套堆和事件循环

Dart 默认不是“多个线程一起摸一个堆”,而是:

  • 每个 isolate 有自己的事件循环
  • 每个 isolate 有自己的堆
  • isolate 之间通过消息传递通信

这带来的直接好处是:

  • 少了大量共享内存锁竞争
  • 少了“这个对象被另一个线程改了”的复杂度

代价是:

  • 不能像 JVM 那样随便共享对象引用
  • 真正要跨 isolate 通信时,需要序列化/消息传递思维

3. GC 都会停顿,但停顿来源和体感不同

JVM

你会遇到:

  • Young GC / Old GC
  • STW(Stop-The-World)
  • 大对象、碎片、晋升压力

在 Android 里,GC 问题常表现为:

  • 掉帧
  • 卡顿
  • 内存抖动
  • OOM 前的频繁回收

Dart / Flutter

Dart 也有垃圾回收,但体感常和:

  • Widget 重建频率
  • 短命对象分配量
  • 大图/大列表/解析任务
  • isolate 是否把重任务隔离出去

一起出现。

Android / Flutter / Web / Backend 对照

维度JVM / AndroidDart / FlutterWeb / JSBackend
默认共享内存多线程共享堆isolate 独立堆单线程主上下文依语言而定
线程私有执行现场栈帧isolate 自己的调用栈调用栈依语言而定
并发风险重点可见性、锁、竞态isolate 间通信、主 isolate 卡顿主线程阻塞数据竞争、线程池/协程治理
回收问题表现GC 卡顿 / OOMUI 卡顿 / 分配抖动 / isolate 边界主线程卡顿吞吐下降 / 堆膨胀

常见场景

1. Android 页面退出了但对象还没释放

常见原因:

  • 单例持有 Activity
  • Handler / Runnable 持有页面引用
  • Observer / callback 没解绑
  • 大集合缓存未清

2. Flutter 页面没了,但控制器/订阅还活着

典型问题:

  • TextEditingControllerdispose
  • stream subscription 没取消
  • 长任务结果回调晚于页面销毁

3. 大 JSON / 大图解析导致卡顿

这类问题常被误以为是“异步就好了”。实际更关键的是:

  • 任务是否还在主执行上下文
  • 是否制造了过多短命对象
  • 是否该拆到 isolate

常见坑

现象修法
以为对象置 null 就一定立刻释放内存没马上降GC 是时机性回收,不是立刻销毁
共享对象不做可见性控制偶现脏读/竞态JVM 下用同步原语;Dart 下优先隔离通信
Flutter 里过度制造临时对象掉帧、GC 抖动减少不必要分配、重任务移出主 isolate
把 isolate 当便宜线程池通信复杂、过度设计只给 CPU 密集任务上 isolate

与相近概念对比

概念关注点更像解决什么问题
JMM多线程共享内存可见性为什么另一个线程能不能看见修改
GC对象何时释放为什么内存不立刻下降
isolate内存隔离与消息通信如何减少共享内存复杂度
生命周期对象应该活多久为什么页面走了对象还活着

对应实验

目前本主题先以理论文档为主;相关对照入口:

复习检查题

  1. 为什么说 JVM 的共享内存复杂度通常高于 Dart isolate?

    :因为 JVM 多线程默认共享堆,对象可被多个线程同时读写,需要额外处理可见性与同步;Dart isolate 默认独立堆,不共享普通对象,天然减少了共享内存竞态。

  2. 为什么页面销毁后对象还可能活着?

    :因为只要还有引用链,GC 就不会回收;页面结束并不等于对象自动失去所有引用。

  3. 什么情况下应优先考虑 isolate?

    :CPU 密集任务、大 JSON 解析、图片处理等主执行上下文容易卡顿的场景,而不是所有异步任务。

速记

  • JVM:共享堆 + 线程栈 + 可见性问题
  • Dart:isolate 独立堆 + 消息传递
  • 回收是时机性,不是立刻销毁
  • 泄漏本质是“引用链没断”