数组竟让项目崩溃!程序员深夜填坑的血泪教训!
最近做项目内存优化遇到一个有趣的问题,内存优化的过程大概是这样的:
对项目中内存占用进行优化,选取一台线上机器dump内存,排查发现:除大量字符串对象外,项目使用了大量HashMap#Node对象。
准备使用 org.eclipse.collections.impl.map.mutable.UnifiedMap 替换 HashMap 实现,减少内存占用。HashSet 也同理。
实现起来比较简单,分析大对象创建位置,使用正则表达式找到Map对象创建的地方,可以批量替换对象实现。这里也体现出遵循代码规范的好处:
Map<A, B> map = new HashMap<>();
Map<A, B> map = new HashMap<>(64);
// 以下这种实现就不太好,还要多一些额外处理
Map<A, B> map = Maps.newHashMap();
当然,这种实现思路需要进行良好的单元测试和回归测试,如果其他代码对Map的依赖仅仅依赖于Map接口,倒没有问题,但是例如序列化、迭代顺序等可能会隐形依赖HashMap,可能会有bug。
自测时遇到如下异常:java.lang.ArrayStoreException
排查异常StackTrace 发现异常发生的方法如下:
void foo(Map<String, BigDecimal>[] prices) {
for (int i = 0; i < prices.length; i++) {
prices[i] = new HashMap<>();
}
}
沿着 StackTrace 进一步找发现数组对象的创建代码如下:
Map<String, BigDecimal>[] prices = new HashMap[n];
这里编译器会提示warning,但是通常都会被忽略。
使用数组+泛型存在类型不安全的问题,prices 数组必须在创建时声明其真实类型,存放时类型不一致就会报 java.lang.ArrayStoreException 异常。
虽然我们的每一步操作都是看似没有问题的,但是bug还是出现了。
// 类型安全示例
List<Map<String, String>> list = new ArrayList<>();
list.add(new HashMap<>());
// 以下类型不安全代码,编译器无法通过
List<HashMap<String, String>> list = new ArrayList<>();
list.add(new HashMap<>());
结论
集合类的使用应该优于数组。