禁用Java整数缓存

4

最近遇到了一个与Java Integer Cache相关的问题,我正在寻找一种禁用它的方法。

现在无法进行测试,因为除非我们使用缓存之外的整数值进行测试,否则无法确定其是否有效。

我们的情况:我们有8个有错误的!=比较,在测试2个月期间一直正常工作,因为我们从未有一个基础数据库实体的主键大于128。


4
我认为在禁用缓存的情况下测试并不是不可能的。如果你想要一个未被缓存的整数,请使用 new Integer(1); 进行初始化。同时,解雇那个使用 != 进行对象比较的人。 - Kayaman
这个故事还有更多的内容。你是如何比较这些整数的?它们都是对象吗?我敢打赌,编写这段代码的开发人员假定在某个时刻会进行拆箱操作,但在你的代码中是否可能出现这种情况呢? - Makoto
2
既然您现在已经了解了这个问题,您可以轻松地更改测试,使主键值大于128。这比尝试黑客攻击“Integer”类要好得多。 - Andreas
4个回答

8
你不需要担心。整数缓存是Integer的实现细节。如果你使用Integer,那么你就接受了这个缓存。它是一个好东西,能够减少有效重复对象的数量。
使用FindBugs来查找虚假的整数引用比较,并用Integer.equals替换它们--这是解决你遇到的问题的正确方法。
如果你的应用程序存在这种基本的问题,我强烈建议运行一套完整的静态分析测试,并逐步处理它们。

1
最简单的解决并捕获这种错误的方法是编写一个单元测试,其中主键任意(和随机)大于128。
这样做可以得到以下结果:
- 从IntegerCache中获取一个整数 - 一种独立验证的方法,无需更改任何运行时参数(即在开发人员框中轻松重复) - 简洁的文档形式,以测试的形式说明您确实希望这些值被考虑
您不需要禁用JVM内部来测试此功能。您只需要了解它们的限制,并在它们的情况下进行测试,如果它们的情况对您的应用程序构成挑战。

0
自从Java 6引入了逃逸分析,我就看不出池化临时对象(如Integer)的好处了。此外,由于Integer缓存有时会是“不可预测的”,这可能会阻止逃逸分析正常运行。
如果启用了Integer缓存,则Java 8逃逸分析无法对整数对象进行标量替换。(Java 13似乎两种方式都做得很好)
虽然官方不支持禁用Integer缓存,但可以使用以下代码片段来实现:
public static Unsafe getUnsafe() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        return (Unsafe) theUnsafe.get(null);
    } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
        e.printStackTrace();
        return null;
    }
}

public static void disableIntegerCache() {
    try {
        Class<?> clazz = Class.forName("java.lang.Integer$IntegerCache");
        Field cache = clazz.getDeclaredField("cache");
        Field low = clazz.getDeclaredField("low");
        Field high = clazz.getDeclaredField("high");

        Unsafe unsafe = getUnsafe();

        Object base = unsafe.staticFieldBase(low);
        long lowOffset = unsafe.staticFieldOffset(low);
        long highOffset = unsafe.staticFieldOffset(high);
        long cacheOffset = unsafe.staticFieldOffset(cache);

        unsafe.putObjectVolatile(base, cacheOffset, new Integer[0]);
        unsafe.putIntVolatile(base, lowOffset, Integer.MAX_VALUE);
        unsafe.putIntVolatile(base, highOffset, Integer.MIN_VALUE);
    } catch (ClassNotFoundException | NoSuchFieldException | IllegalArgumentException e) {
        e.printStackTrace();
    }
}

我使用MAX_VALUE和MIN_VALUE是因为在valueOf方法中与JVM内部机制相关的奇怪行为。


-2

您可以使用Java选项设置缓存大小。以下内容将让您根据需要设置缓存:

-XX:AutoBoxCacheMax=<CACHE_SIZE>

例如:

-XX:AutoBoxCacheMax=1

2
你不能用这个方法来减少缓存大小。如果 127 大于此值,则使用 127。请查看此行 - Michael
@GhostCat:如果它不是真正的答案,那么它怎么可能解决任何问题呢?! - Makoto
@AR1:从技术角度来看,“正确”的答案可能在实际情况下仍然是错误的答案。 - Makoto
@GhostCat @AR1 IntegerCache 中的代码仍将其设置为 [-128, 127]。此选项只能增加默认值,-XX:AutoBoxCacheMax=1 将不会产生任何影响。 - Karol Dowbecki
@Makoto 有时候可能存在争议的余地。如果它确实能够起作用,那么它可能是一个快速解决方案,例如在重新设计解决方案的时间内让事情继续运行。 - GhostCat
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接