Profiled与Non-profiled代码分段存储是为了避免缓存碎片化导致编译中断:Profiled段(C1+采样)小而频繁更新,Non-profiled段(C2深度优化)大且稳定驻留,分段后可独立管理回收与扩容,确保JIT持续在线。

为什么 Profiled 和 Non-profiled 代码要分开存
JDK 9 起启用 Segmented Code Cache,核心不是“多分几块内存”本身,而是让不同编译阶段、不同用途的代码互不干扰。Profiled code(C1 编译 + 性能采样)体积小、更新频繁、生命周期短;Non-profiled code(C2 深度优化后产物)体积大、稳定、长期驻留。混在一块连续缓存里,容易因碎片化导致提前触发 CodeCache is full… The compiler has been disabled。分段后,JVM 可独立管理各段回收、扩容、刷新策略。
怎么配 ProfiledCodeHeapSize 和 NonProfiledCodeHeapSize
默认值对多数服务偏保守——尤其 Spring Boot + 多模块依赖场景下,Profiled 段常被撑满,而 Non-profiled 段仍有余量。关键不是盲目调大总 ReservedCodeCacheSize,而是按实际编译行为重分配:
-
-XX:ProfiledCodeHeapSize=128m:适合启动期大量类加载+预热的场景,避免 C1 编译中途被阻塞 -
-XX:NonProfiledCodeHeapSize=256m:C2 编译产物更“重”,尤其含大量内联、向量化优化时,需预留足够空间 - 两段之和必须 ≤
ReservedCodeCacheSize,否则 JVM 启动失败并报错Invalid -XX:ReservedCodeCacheSize=xxx, must be >= sum of all code heap sizes - 若应用基本不用 C1(如已用
-XX:TieredStopAtLevel=1关闭分层编译),可设-XX:ProfiledCodeHeapSize=4m,腾出空间给 Non-profiled 段
哪些参数会意外覆盖分段行为
看似无关的 JVM 参数可能让 Segmented Code Cache “退化”回 JDK 8 的扁平结构:
-
-XX:-UseSegmentedCodeCache:显式关闭,直接失效(极少用,但配置模板里可能残留) -
-XX:InitialCodeCacheSize或-XX:ReservedCodeCacheSize设得过小(如 CodeCache is not segmented due to small size -
-XX:+UseCodeCacheFlushing开启后,若未配合-XX:CodeCacheMinimumFreeSpace,可能导致 Profiled 段频繁刷出,反而加剧抖动
怎么验证分段是否生效且各段使用均衡
光看启动参数不够,得查运行时状态:
- 加
-XX:+PrintCodeCache:每次 GC 后打印各段剩余空间,观察 Profiled 段是否长期 >90% 使用率 - jstat -compiler
pid:关注failed列是否持续增长,说明某段已满导致编译失败 - jcmd
pidVM.native_memory summary scale=MB:搜索code行,确认三段(non-method / profiled / non-profiled)均有非零分配 - 注意:
jmap -histo或jstack不反映 Code Cache 状态,别在这上面浪费时间

















