Java堆中大量ZoneRegion实例,ZoneId.of未缓存是预期还是JVM bug?
问题分析与解答
问题背景
我有大量包含ZoneId tz字段的对象,所有tz字段实例均通过ZoneId.of静态方法创建。选择ZoneId而非普通String,是因为预期ZoneId实例会被缓存(毕竟时区数量有限)。但堆分析显示,每次使用相同参数调用ZoneId.of都会创建新的ZoneRegion实例:
ZoneId zoneId1 = ZoneId.of("Europe/Kiev"); // 分配新的ZoneRegion ZoneId zoneId2 = ZoneId.of("Europe/Kiev"); // 分配新的ZoneRegion
使用的JDK版本:
openjdk version "21.0.1" 2023-10-17 OpenJDK Runtime Environment (build 21.0.1+12-Ubuntu-222.04) OpenJDK 64-Bit Server VM (build 21.0.1+12-Ubuntu-222.04, mixed mode, sharing)
结论
这种情况不是JVM bug,而是过时时区别名导致的非预期缓存失效。
原因说明
JDK的ZoneId确实会对标准时区ID进行全局缓存,但你使用的Europe/Kiev是过时的时区别名(对应标准ID为Europe/Kyiv)。在处理这类别名时,ZoneId.of()会完成别名到标准ID的解析,但返回的ZoneRegion实例并不会被加入全局缓存,因此每次调用都会创建新对象。
验证与解决
验证代码
// 过时别名:每次调用生成新实例 ZoneId zoneId1 = ZoneId.of("Europe/Kiev"); ZoneId zoneId2 = ZoneId.of("Europe/Kiev"); System.out.println(zoneId1 == zoneId2); // 输出 false // 标准ID:复用缓存实例 ZoneId zoneId3 = ZoneId.of("Europe/Kyiv"); ZoneId zoneId4 = ZoneId.of("Europe/Kyiv"); System.out.println(zoneId3 == zoneId4); // 输出 true
解决办法
- 改用标准时区ID:通过
ZoneId.getAvailableZoneIds()获取当前所有有效标准时区ID,替换代码中的过时别名; - 自定义缓存:如果必须保留过时别名,可自行实现缓存层,例如用
ConcurrentHashMap缓存通过别名创建的ZoneId实例:
private static final Map<String, ZoneId> ZONE_ID_CACHE = new ConcurrentHashMap<>(); public static ZoneId getCachedZoneId(String zoneId) { return ZONE_ID_CACHE.computeIfAbsent(zoneId, ZoneId::of); }
内容的提问来源于stack exchange,提问作者Dmitriy Dumanskiy
相关产品推荐
相关产品推荐

