订单查询服务堆里 char[] 和 String 占 2.1G,jmap 直方图里 String 有 320 万个,代码里对订单号统一调用了 intern。碰到这种情况,我一般先确认改动有没有真的生效。
原因不复杂,intern 把字符串放进常量池,JDK 8 以后池在堆里,只增不减,只有 Full GC 才可能清理。订单号本身唯一,去重没有收益,白白多存一份引用和表项。
「字符串常量池膨胀」相关的日志我一般连着看前后五分钟,单看一条报错经常判断不出是哪一步失败。报错里带路径的,先去看那个路径在不在、权限对不对。
jcmd 21455 GC.class_histogram | grep -E 'String|char\[\]' jmap -histo:live 21455 | head -10 jstat -gc 21455 60000 10
同一份配置存在多处的,先确认改的是被加载的那一份。新版本先放一台机器上跑一天再全量,问题一般在这段时间暴露。
具体做法是,去掉订单号上的 intern 调用 只对枚举型短字符串保留;用 jcmd GC.class_histogram 对比改动前后的 String 数量;如果确实要缓存 用有上限的 Caffeine 而不是常量池;观察老年代在 Full GC 后是否能回落。
最后用这个标准收尾,String 实例数下降一个数量级 老年代占用在 Full GC 后能降到 2G 以下。
