Java基本数据类型缓存池解析-源码剖析

发布于:2025-05-12 ⋅ 阅读:(21) ⋅ 点赞:(0)

抛出问题:new Integer(18) 与 Integer.valueOf(18) 的区别是什么?

  • new Integer(18) 每次都会新建一个对象;
  • Integer.valueOf(18) 会使⽤用缓存池中的对象,多次调用只会取同⼀一个对象的引用
Integer x = new Integer(18);
Integer y = new Integer(18);
System.out.println(x == y);

Integer z = Integer.valueOf(18);
Integer k = Integer.valueOf(18);
System.out.println(z == k);

Integer m = Integer.valueOf(300);
Integer p = Integer.valueOf(300);
System.out.println(m == p);
来看一下输出的结果吧

除了Float和Double之外,其他的六个包装器类(Byte、Short、Integer、Long、Character、Boolean)都有常量缓存池。

为什么那两个没有呢? 很好思考,缓存这一思想,简单理解,是把常用的准备好。

首先,Float、Double。都是不可穷举,就算缓存也不知道缓存1.0、还是1.1等等。但是Boolean,仅仅只有true&false,还有这些整数类都一样(也不是全缓存,而是都缓存了255)。

  • Byte:-128~127,也就是所有的 byte 值
  • Short:-128~127
  • Long:-128~127
  • Character:\u0000 - \u007F
  • Boolean:true 和 false
  • Integer:-128~127

为了知道为什么,来跟随笔者阅读valueof的源码看看吧(bushi~,还是先看下面的注释吧)。

    @IntrinsicCandidate
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

贴出注释:

翻译: 返回一个Integer示例,表示指定的int 值。如果不需要一个新的Integer实例,则通常应优先使用此方法,而不是构造函数Integer(int),因为此方法可能通过缓存频繁请求的值来产生更好的空间和时间性能。此方法将始终缓存-128到127(包括两端)范围内的值,

参数: i 一个 int值

返回值:一个表示Integer的实例 i

自1.5起

从这里发现,我们还需要看看IntegerCache这个静态内部类的源码:

private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;
        static Integer[] archivedCache;

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    h = Math.max(parseInt(integerCacheHighPropValue), 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            // Load IntegerCache.archivedCache from archive, if possible
            CDS.initializeFromArchive(IntegerCache.class);
            int size = (high - low) + 1;

            // Use the archived cache if it exists and is large enough
            if (archivedCache == null || size > archivedCache.length) {
                Integer[] c = new Integer[size];
                int j = low;
                for(int i = 0; i < c.length; i++) {
                    c[i] = new Integer(j++);
                }
                archivedCache = c;
            }
            cache = archivedCache;
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

同样,先来看看注释:

翻译:

缓存支持JLS要求的自动装箱的对象标识语义,适用于-128和127之间的值(包含)。缓存在首次使用时初始化。缓存的大小可以通过-XX:AutoBoxCacheMax=<size> 选项来控制。在VM初始化期间,java.lang.Integer.IntegerCache.high属性可以设置并保存在jdk.internal.misc的私有系统属性中。VM类。警告:缓存与CDS一起存档,并在运行时从共享*存档中重新加载。归档缓存(Integer[])和Integer对象位于封闭的归档堆区域中。更改实现时应小心,初始化后不应为缓存数组分配新的Integer对象。

说人话:就是通过Integer.valueOf()方法获取整数对象时,会先检查该整数是否存在IntegerCache中,如果在,则返回缓存中的对象,否则创建一个新的对象并缓存起来。

 assert IntegerCache.high >= 127;

这里这个assert,是一个关键字,寓意是断言,为了方便表示程序,并不是发布程序的组成部分。

默认情况下,断言是关闭的,可以在命令行允许java程序时候加入 -ea参数来打开断言。

来看这个代码:

public class Test {
    public static void main(String[] args) {
        int high = 126;
        assert high >= 127;
    }
}

在这里通过命令行,java -ea参数可以看到报错了。这里我们断言high>=127.当不满足时候,就会报错。实际上不止Java有这种设计,C也有,感兴趣的可以去了解。

在Java中,针对一些基本数据类型,Java会在程序启动时候创建一些常用的对象并缓存在内存中,以提高程序的性能和节省内存开销。这些常用对象缓存在一个固定的范围内,超过这个范围的值会被重新创建新的对象。

使用数据类型缓存池可以有效提高程序的性能和节省内存开销,但需要注意的是,在特定业务下,缓存池可能带来一些问题,比如,缓存池中的对象被不同的线程同时修改,导致数据错误等问题。因此,实际开发中,需要根据具体的业务需求来决定是否需要使用数据类型缓存池。


网站公告

今日签到

点亮在社区的每一天
去签到