要让函数在jit下稳定进入单态状态,关键在于保持接收者类型和对象结构完全一致:统一用class构造、初始化即定型字段、避免动态增删属性或原型修改,并通过--trace-ic或-xx:+printmethoddata等工具验证ic是否真正固化为monomorphic。

要让函数在 JIT 编译器下稳定进入单态(Monomorphic)状态,核心不是“写得快”,而是“让运行时看到的类型足够单一、结构足够稳定”。IC 本身不主动优化多态,它只忠实地记录和加速你实际喂给它的行为。一旦调用点反复遇到同一隐藏类(或 JVM 中的同一具体类型),IC 就会固化为单态路径,生成硬编码偏移或直接跳转,性能接近静态调用。
保持对象形状完全一致
单态缓存失效最常见的原因不是类型名不同,而是对象内部结构(即隐藏类或 klass)不一致。V8 和 JVM 都将属性顺序、初始字段、是否使用 __slots__ 或 final 字段等视为结构差异。
- 统一用 class 构造对象,避免混用字面量:用 new Point(1, 2),而不是有时 {x: 1, y: 2},有时 {y: 2, x: 1}
- 初始化时一次性声明所有字段,不后续动态添加:避免 obj.z = 3 或 delete obj.y,这会强制创建新隐藏类
- 在 Java 中,优先用 final 字段 + 不可变类;在 JS/TS 中,可用 Object.freeze()(仅开发期辅助验证)或构造后禁止扩展
收敛调用点的接收者类型
一个方法调用是否能长期维持单态,取决于每次调用时 this(或 receiver)的实际类型是否始终落在同一个隐藏类 / 具体子类上。
- 避免把多个子类实例传给同一个泛型函数:比如 process(item) 同时接收 User、Admin、Guest 实例,哪怕它们都实现同一接口
- 在 Java 中,对热路径方法,优先使用 private 或 final 实例方法——JIT 可通过类层次分析(CHA)确认无重写,直接内联并绑定到唯一实现
- 在 JS 中,若必须多态,可拆分为专用函数:如 processUser(u)、processAdmin(a),各自形成独立单态调用点
避免触发 IC 升级的运行时操作
IC 状态升级(uninit → monomorphic → polymorphic → megamorphic)是不可逆的降级过程。一次超态退化后,即使后续输入变单一,JIT 通常也不会自动回退到单态,需等待反馈向量重置或方法重编译。
- 禁用运行时原型修改:Object.prototype.newMethod = ... 会让所有对象的属性访问 IC 失效
- 避免在热函数中使用 for...in、Object.keys()、Reflect.* 等通用反射操作,它们绕过 IC 路径
- 日志或调试代码别留在热路径里:console.log(obj.x) 的 obj 若结构不稳,会污染该属性的 LoadIC 状态
用工具确认单态是否真正生效
不要凭感觉判断——必须观测运行时反馈。不同引擎提供不同诊断方式:
- V8:启动 Node.js 时加 --trace-ic,观察关键调用点是否长期停留在 monomorphic;配合 %DebugPrint(func) 查看 feedback vector 中对应插槽是否只记录 1 种隐藏类
- JVM:用 -XX:+PrintInlining 看是否出现 inline (hot),再结合 -XX:+PrintMethodData 检查该 invokevirtual 是否标记为 monomorphic
- 注意:-XX:+PrintCompilation 只告诉你方法被编译了,不反映 IC 状态,不能用于验证单态










