-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathint-cache.md
More file actions
185 lines (130 loc) · 7.83 KB
/
int-cache.md
File metadata and controls
185 lines (130 loc) · 7.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
---
title: Java基本数据类型缓存池剖析(IntegerCache)
shortTitle: Java基本数据类型缓存池
category:
- Java核心
tag:
- Java重要知识点
description: 本文详细介绍了Java基本数据类型缓存池,包括其工作原理、应用场景以及如何使用缓存池提高内存利用效率。通过本文,您将了解到Java基本数据类型缓存池的优化策略和实践,掌握如何在实际开发中合理使用缓存池,提高程序性能。
head:
- - meta
- name: keywords
content: Java, 基本数据类型, 缓存池, 内存优化, 缓存池原理, 缓存池应用, 缓存池实践
---
“三妹,今天我们来补一个小的知识点:Java 基本数据类型缓存池。”我喝了一口枸杞泡的茶后对三妹说,“考你一个问题哈:`new Integer(18) 与 Integer.valueOf(18)` 的区别是什么?”
“难道不一样吗?”三妹有点诧异。
“不一样的。”我笑着说。
- `new Integer(18)` 每次都会新建一个对象;
- `Integer.valueOf(18)` 会使⽤用缓存池中的对象,多次调用只会取同⼀一个对象的引用。
来看下面这段代码:
```java
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);
```
来看一下输出结果吧:
```
false
true
false
```
“第一个 false,我知道原因,因为 new 出来的是不同的对象,地址不同。”三妹解释道,“第二个和第三个我认为都应该是 true 啊,为什么第三个会输出 false 呢?这个我理解不了。”
“其实原因也很简单。”我胸有成竹地说。
基本数据类型的包装类除了 Float 和 Double 之外,其他六个包装器类(Byte、Short、Integer、Long、Character、Boolean)都有常量缓存池。
- Byte:-128~127,也就是所有的 byte 值
- Short:-128~127
- Long:-128~127
- Character:\u0000 - \u007F
- Boolean:true 和 false
拿 Integer 来举例子,Integer 类内部中内置了 256 个 Integer 类型的缓存数据,当使用的数据范围在 -128~127 之间时,会直接返回常量池中数据的引用,而不是创建对象,超过这个范围时会创建新的对象。
18 在 -128~127 之间,300 不在。
来看一下 valueOf 方法的源码吧。
```java
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 这个内部类的原因啊!”三妹好像发现了新大陆。
“是滴。来看一下 IntegerCache 这个静态内部类的源码吧。”
```java
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 Integer.IntegerCache.high >= 127;
}
private IntegerCache() {}
}
```
详细解释下:当我们通过 `Integer.valueOf()` 方法获取整数对象时,会先检查该整数是否在 IntegerCache 中,如果在,则返回缓存中的对象,否则创建一个新的对象并缓存起来。
需要注意的是,如果使用 `new Integer()` 创建对象,即使值在 -128 到 127 范围内,也不会被缓存,每次都会创建新的对象。因此,推荐使用 `Integer.valueOf()` 方法获取整数对象。
[学习 static 关键字](https://javabetter.cn/oo/static.html)的时候,会详细解释静态代码块,你暂时先记住,三妹,静态代码块通常用来初始化一些静态变量,它会优先于 main() 方法执行。
在静态代码块中,low 为 -128,也就是缓存池的最小值;high 默认为 127,也就是缓存池的最大值,共计 256 个。
*可以在 JVM 启动的时候,通过 `-XX:AutoBoxCacheMax=NNN` 来设置缓存池的大小,当然了,不能无限大,最大到 `Integer.MAX_VALUE -129`*
之后,初始化 cache 数组的大小,然后遍历填充,下标从 0 开始。
“明白了吧?三妹。”我喝了一口水后,扭头看了看旁边的三妹。
“这段代码不难理解,难理解的是 `assert Integer.IntegerCache.high >= 127;`,这行代码是干嘛的呀?”三妹很是不解。
“哦哦,你挺细心的呀!”三妹真不错,求知欲望越来越强烈了。
assert 是 Java 中的一个关键字,寓意是断言,为了方便调试程序,并不是发布程序的组成部分。
默认情况下,断言是关闭的,可以在命令行运行 Java 程序的时候加上 `-ea` 参数打开断言。
来看这段代码。
```java
public class AssertTest {
public static void main(String[] args) {
int high = 126;
assert high >= 127;
}
}
```
假设手动设置的缓存池大小为 126,显然不太符合缓存池的预期值 127,结果会输出什么呢?
直接在 Intellij IDEA 中打开命令行终端,进入 classes 文件,执行:
```
/usr/libexec/java_home -v 1.8 --exec java -ea com.itwanger.s51.AssertTest
```
*我用的 macOS 环境,装了好多个版本的 JDK,该命令可以切换到 JDK 8*
也可以不指定 Java 版本直接执行(加上 `-ea` 参数):
```
java -ea com.itwanger.s51.AssertTest
```
“呀,报错了呀。”三妹喊道。
```
Exception in thread "main" java.lang.AssertionError
at com.itwanger.s51.AssertTest.main(AssertTest.java:9)
```
“是滴,因为 126 小于 127。”我回答道。
“原来 assert 是这样用的啊,我明白了。”三妹表示学会了。
在 Java 中,针对一些基本数据类型(如 Integer、Long、Boolean 等),Java 会在程序启动时创建一些常用的对象并缓存在内存中,以提高程序的性能和节省内存开销。这些常用对象被缓存在一个固定的范围内,超出这个范围的值会被重新创建新的对象。
使用数据类型缓存池可以有效提高程序的性能和节省内存开销,但需要注意的是,在特定的业务场景下,缓存池可能会带来一些问题,例如缓存池中的对象被不同的线程同时修改,导致数据错误等问题。因此,在实际开发中,需要根据具体的业务需求来决定是否使用数据类型缓存池。
----
GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https://github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括Java基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 10000+ 的 Java 教程](https://javabetter.cn/overview/)
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **222** 即可免费领取。
