Java基本数据类型的大小
type |
size(bits) |
bytes |
boolean |
8 |
1 |
byte |
8 |
1 |
char |
16 |
2 |
short |
16 |
2 |
int |
32 |
4 |
long |
64 |
8 |
float |
32 |
4 |
double |
64 |
8 |
Java引用的大小
在 32 位的 JVM 上,一个对象引用占用 4 个字节;在 64 位JVM上,占用 8 个字节。
使用 8 个字节是为了能够管理大于 4G 的内存,如果你的程序不需要访问大于 4G 的内存,
可通过 -XX:+UseCompressedOops 选项,开启指针压缩。从 Java 1.6.0_23 开始,这个选项默认是开的。
Java对象头的大小
在32位JVM中,对象头的大小为8个字节(4字节的Mark Word+4字节的Klass Pointer).
在64位JVM上,占用16个字节(8字节的Mark Word+8字节的Klass Pointer),因为开启UseCompressedOops,所以实际占用12个字节(8字节的Mark Word+4字节的Klass Pointer) 。参考klass pointer
接下来的内容都基于64位的JVM来展开
Java对象的大小
1、任意Java对象都包含至少12个字节的Object Header。
2、JVM分配内存以8字节为基本单位,如果不满小于8字节,则向8字节的倍数补齐。参考8 byte alignment
思考
Object object = new Object(); 占用多少内存?
数组的大小如何计算?
验证
添加Maven依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
import org.openjdk.jol.info.ClassLayout;
/**
* Created by jianpingpan on 2019/1/17.
*/
public class BasicClass {
public static void main(String[] args) throws Exception {
System.out.println(ClassLayout.parseClass(Object.class).toPrintable());
System.out.println(ClassLayout.parseClass(String.class).toPrintable());
System.out.println(ClassLayout.parseClass(byte[].class).toPrintable());
System.out.println(ClassLayout.parseClass(char[].class).toPrintable());
}
}
byte[] 和char[]的 object header为16个字节是因为有4个字节的数组长度。
String / char[] / byte[] 内存大小计算
String a = new String("abc");
String b = new String("abcd");
String c = new String("abc");
第一行占用JVM内存的大小:
对象大小 = 12字节(object header)+
4字节 (hash)+
4字节(数组引用vlaue[]) +
4字节 (padding)
16字节+3*2字节+2字节padding (数组value[])
= 48字节
假设要缓存的字符个数为N。
String的内存大小计算公式 = 40+N*2 +padding
char数组的内存大小计算公式 = 16+N*2+padding
如果用byte数组来存储字符串数据,占用的内存大小X需要分2种情况讨论:
1、如果需要存储的字符全在ASCII码中,一个字符用一个byte就可以存储 (编码方式可选ISO-8859-1/GBK/UTF-8):
X = 16+N+padding
2、如果需要存储的字符范围不能被ASCII码覆盖,则需要根据字符范围确定合适的存储方式。
如需要要存储字符集为ASCII+中文字符,则可使用GBK编码:
16+N+padding <X < 16+N*2+padding
如果字符集不能被ASCII码覆盖,并且包含非中文字符,则使用UTF-8编码:
16+N+padding<X<16+6*N+padding
结论:
由此可见,char数组占用的内存大小小于String占用的内存大小。
若存储的字符范围以ASCII码为主,使用byte数组存储优于char数组。
实际使用场景
那么在缓存中可以直接用char[]或byte[]替换String么?
把
Set<String> set = new HashSet<>();
替换成
Set<byte[]> set = new HashSet<>();
会怎样呢?
很明显,contains方法、get方法都会失效。因为每个byte[]的hashCode不一样。
我们用下面的这个ByteArray/CharArray封装byte[],再用ByteArray替换String。
/**
* Created by jianpingpan on 2019/1/23.
*/
public class ByteArray {
byte[] bytes;
public ByteArray(byte[] bytes){
this.bytes = bytes;
}
@Override
public int hashCode() {
if(null == bytes){
return 0;
}
return new String(bytes).hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}
return hashCode()==obj.hashCode();
}
}
(CharArray的实现方式同ByteArray,只是把byte[] bytes 替换成 char[] chars即可)
ByteArray占用的内存大小 =
12字节(object header+
4字节(数组引用bytes[]) +
16字节+N字节+padding (数组bytes[])
= 32字节+N字节+padding
CharArray占用的内存大小=
12字节(object header+
4字节(数组引用bytes[]) +
16字节+2*N字节+padding (数组bytes[])
= 32字节+2*N字节+padding
其中,N为数组中元素的个数。
例子
以存储100万条长度为32位的MD5字符串为例且内容互不相同字符串为例(假设字符串中的字符均为字母、数字、下划线)。
可以用classmexer来计算内存使用量 。
import com.javamex.classmexer.MemoryUtil;
/**
* Created by jianpingpan on 2019/1/25.
*/
public class StringTest {
public static void main(String[] args){
String s="cfcd208495d565ef66e7dff9f98764da";
ByteArray b = new ByteArray(s.getBytes());
CharArray c = new CharArray(s.toCharArray());
long stringBytes = MemoryUtil.deepMemoryUsageOf(s);
long byteArrayBytes = MemoryUtil.deepMemoryUsageOf(b);
long charArrayBytes = MemoryUtil.deepMemoryUsageOf(c);
System.out.println("stringBytes:"+stringBytes);
System.out.println("byteArrayBytes:"+byteArrayBytes);
System.out.println("charArrayBytes:"+charArrayBytes);
}
}
用String存储,每条记录占用的空间为 40+32*2 = 104字节
用ByteArray存储,每条记录占用的空间为 32+32 = 64字节
用CharArray存储,每条记录占用的空间为 32+32*2 = 96字节
参考文档:
http://btoddb-java-sizing.blogspot.com/2012/01/object-sizes.html
https://stackoverflow.com/questions/26357186/what-is-in-java-object-header/26416983
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html