在以前的工作中,曾经遇到过这么一个事儿:某个项目的基础common包中有一个树结构。它的节点如下定义:
那是上生产环境之前没有做测试吗?
做了。
上生产之前分别跑了单元测试,测试环境集成测试和灰度环境发布。但在这些环境里运行都正常。
经过排查以后,发现是这个树结构的行为出现了一定概率的错误。很快,root cause被找到了,Node节点的定义被改成了如下形式:
之前代码逻辑中使用
为什么?
这要从java的Integer缓存策略说起:
在jdk 5以后的运行时中,在默认的配置参数下,执行如下代码:
原因是在 Java 5 中,为 Integer 的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用。
上面的规则默认适用于整数区间 -128 到 +127。(后面版本增加了
这种 Integer 缓存策略仅在自动装箱(autoboxing)的时候有用,使用构造器创建的 Integer 对象不能被缓存。
Java 编译器把原始类型自动转换为封装类的过程称为自动装箱(autoboxing),这相当于调用 valueOf 方法。
Integer.valueOf()方法的实现是:
那为什么除生产环境之外的其他环境都是对的呢?
因为测试环境和灰度环境的树结构只有十几个节点。。而生产环境的树结构有几百个节点。。
public class Node { private int id; private int parentId; //getters and setters and other methods }之前这个工程运行都正常。但在某次升级之后,几个与升级逻辑无关的业务突然开始出现莫名其妙的行为混乱而且没有任何异常。由于这几个服务的压力较大,短时间内出现了大量的脏数据。
那是上生产环境之前没有做测试吗?
做了。
上生产之前分别跑了单元测试,测试环境集成测试和灰度环境发布。但在这些环境里运行都正常。
经过排查以后,发现是这个树结构的行为出现了一定概率的错误。很快,root cause被找到了,Node节点的定义被改成了如下形式:
public class Node { private Integer id; private Integer parentId; //getters and setters and other methods }这个类里面所有的int都被替换成了Integer。
之前代码逻辑中使用
if( nodeA.getId() == nodeB.getParentId())的判断有很多原本应该是true的都变成了false。
为什么?
这要从java的Integer缓存策略说起:
在jdk 5以后的运行时中,在默认的配置参数下,执行如下代码:
@Test public void test() { Integer a = 10; Integer b = 10; System.out.println(a == b); //true Integer c = 200; Integer d = 200; System.out.println(c == d); //false }结果一个是true,一个是false。
原因是在 Java 5 中,为 Integer 的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用。
上面的规则默认适用于整数区间 -128 到 +127。(后面版本增加了
-XX:AutoBoxCacheMax=<size>配置参数)
这种 Integer 缓存策略仅在自动装箱(autoboxing)的时候有用,使用构造器创建的 Integer 对象不能被缓存。
Java 编译器把原始类型自动转换为封装类的过程称为自动装箱(autoboxing),这相当于调用 valueOf 方法。
Integer.valueOf()方法的实现是:
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }在创建新的 Integer 对象之前会先在 IntegerCache.cache 中查找。有一个专门的 Java 类IntegerCache来负责 Integer 的缓存。其实现如下:
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }这就很明显了。。在默认的-128~127之间,用==判断Integer相等返回true,在这个范围之外,用==判断Integer相等返回false。
那为什么除生产环境之外的其他环境都是对的呢?
因为测试环境和灰度环境的树结构只有十几个节点。。而生产环境的树结构有几百个节点。。