一.常用类型占用内存大小
java中常用类型所占内存大小,以下值以实测为准。
类型 | 数量 | 大小 | 备注 |
byte | 1 | 1B | |
byte | 1024 | 1KB | |
int | 1 | 4B | |
int | 1024 | 4KB | |
boolean | 1 | 1B | |
boolean | 1024 | 1KB | |
double | 1 | 8B | |
double | 1024 | 8KB | |
long | 1 | 8B | |
long | 1024 | 8KB | |
float | 1 | 4B | |
float | 1024 | 4KB | |
char | 2 | 2B | |
char | 1024 | 2KB | |
Object | 1 | 4B | 对象内存地址 |
Object | 1024 | 4KB | |
Object | 1 | 16B | 对象头 |
String | 1 | 2B | 在java内存中 |
二.String占用内存大小
接下来,我们介绍下String在 java中的内存分配的情况。
先说一下下面的几个小题目的设计用义。
首先,长度设置为64*1024,因为小于44*1024时,字符串对象不会放入堆空间,会让我们不方便统计。
其次,直接写到代码中,而不是从文件中读取,也是考虑到避免内存干扰的原因。
题目1:String在java中的内存大小
首先,我们先出这样的一题目:
public void testMemory() {
String s = "这只一个很长的字符串,有1024长度...";//1024长度的字符串,这里就不真的打到1024长度了。
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 64; i++) {
builder.append(s);
}
}
上面的这段代码,在java内存中会申请多大的内存空间?PS:java中默认使用的是UTF-8格式。
你的第一直觉是不是1024*64*3=192KB,因为UTF-8占用三个字节嘛,但是这个答案其实是错的。
标准答案其实是128KB。java默认确实是UTF-8的格式,但是什么格式其实和在java内存中是没有关系的,java内存中String的实现是char,char占2B,所以1024*1024长度的字符串,所占的内存空间是1024*64*2B=128KB。
而UTF-8编码的作用,是在这个字符串转换成byte[]的时候生效,所以下面代码中的byte1数组所占的内存空间大小是3KB,而如果使用gbk格式转换成byte2数组,那么这个数组所占空间大小为2KB。
String s = "这只一个很长的字符串,有1024长度...";//1024长度的字符串,这里就不真的打到1024长度了。
byte[] bytes1 = s.getBytes();//默认UTF-8格式,UTF-8中种问字符或标点2B,英文符号或标点1B
byte[] bytes2 = s.getBytes("gbk");//如果使用gbk格式,中文字符或标点2B,英文符号或标点1B
题目2:String对象的实现结构
我们再出一道题,下面代码中,所占用的内存空间大小为多少?
public void testMemory() {
String s = "这只一个很长的字符串,有1024长度...";//1024长度的字符串,这里就不真的打到1024长度了。
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 128; i++) {
builder.append(s);
}
builder.toString();
}
你的答案是不是256KB,刚才刚刚才讲过嘛。但是这里的答案是512KB,原因是因为多了一个builder.toString()。这时候,其实等于生成一个新的String对象,这个对象中包含一个128*1024长度的char数组,所占用空间为128KB,加上原来所占用的256KB,自然就是512KB了。
我们看下相关的实现代码:
//StringBuilder类
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
//String类
public String(char value[], int offset, int count) {
...
//下面代码完成了char数组的拷贝
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
toString的时候,新生成了一个对象,并且完成了char数组的拷贝。
题目3:扩容
最后,我们再出一道题,我们把下面的长度从128改成129,会怎样呢。答案会是2B*1024*129=258KB吗,这时候,你也许学聪明了,按照经验应该肯定不是258KB了,那应该是多少呢?
public void testMemory() {
String s = "这只一个很长的字符串,有1024长度...";//1024长度的字符串,这里就不真的打到1024长度了。
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 128; i++) {
builder.append(s);
}
}
这时候的答案其实是512KB。更精准一点的话,应该是当前builder对象中value数组的长度*2+2。
因为在我们写代码的时候,肯定做不到需要多少长度就申请多少长度。所以,StringBuilder中会有一个扩容策略,无参构造时,默认长度为16。每次扩容时,新的数组长度为oldLenght*2+2,相关如下。
//AbstractStringBuilder类中
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
三.验证
我上面的结论,或者你不是很信服,谁知道是不是你个糟老头子来坑骗我的。又或者你就是想了解下,String在JVM中内存的真实分配情况,所以,我们就真的来做几个实验一一验证。
验证1:String占用内存=长度*2B
首先,我们配置一下虚拟机参数-XX:+PrintGCDetails,让程序运行结束时打印内存状态。
分别验证0*1024,64*1024和128*1024长度两种情况:
public void testMemory() {
//这个字符串,长度真的是1024
String s = "赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣";
StringBuilder builder = new StringBuilder();
int length = 64;//修改这里的length长度即可
for (int i = 0; i < 64; i++) {
builder.append(s);
}
//这里很关键
System.gc();
}
这里讲一下我们为什么要主动触发一次GC。因为数组是不断扩容的,并且虚拟机也有可能在过程中产生一些临时对象,通过full GC释放掉这些临时对象的内存,强制存活对象进入老年代,避免了过程中对象内存的干扰。下同。
结果如下:
长度 | ParOldGen大小(单位KB) | 增长空间(单位KB) | 占用空间(KB) | 字符串长度 | 单个大小 |
0 | 381 | ||||
64 | 509 | 128 | 128 | 64*1024 | 2B |
128 | 641 | 132 | 256 | 128*1024 | 2B |
这里381KB的内存空间为初始空间,这块空间被程序中其它的对象所占用的,我们可以看成一个常量基本不会变的。
相关运行后的内存截图:
验证2:String对象的实现就是char[]
分别验证有无builder.toString()这行代码的两种场景即可。
public void testMemory() {
//这个字符串,长度真的是1024
String s = "赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣";
StringBuilder builder = new StringBuilder();
int length = 128;//修改这里的length长度即可
for (int i = 0; i < length; i++) {
builder.append(s);
}
//分别验证有没有下面这句代码两种情况
String ss = builder.toString();//一定要加方法内对象ss,否则会被编译器优化掉不执行
System.gc();
}
结果列表如下:
场景 | ParOldGen大小(单位KB) | 增长空间(单位KB) | 占用空间(KB) |
无builder.toString(); | 641 | 256 | |
有builder.toString(); | 897 | 256 | 512 |
有这行代码时的内存状态:
验证3:扩容
分别验证128*1024和129*1024长度两种情况即可。
public void testMemory() {
//这个字符串,长度真的是1024
String s = "赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣赣";
StringBuilder builder = new StringBuilder();
int length = 128;//修改这里的length长度即可
for (int i = 0; i < length; i++) {
builder.append(s);
}
System.gc();
}
结果如下:
长度 | ParOldGen大小(单位KB) | 增长空间(单位KB) | 占用空间(KB) | 字符串长度 |
128 | 641 | 256 | 128*1024 | |
129 | 897 | 256 | 512 | 129*1024 |
所以,证明了是字符串翻倍扩容的。