本文共 3870 字,大约阅读时间需要 12 分钟。
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 |
在 32 位的 JVM 上,一个对象引用占用 4 个字节;在 64 位JVM上,占用 8 个字节。
使用 8 个字节是为了能够管理大于 4G 的内存,如果你的程序不需要访问大于 4G 的内存,
可通过 -XX:+UseCompressedOops 选项,开启指针压缩。从 Java 1.6.0_23 开始,这个选项默认是开的。
在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) 。参考
接下来的内容都基于64位的JVM来展开
1、任意Java对象都包含至少12个字节的Object Header。
2、JVM分配内存以8字节为基本单位,如果不满小于8字节,则向8字节的倍数补齐。参考
思考
Object object = new Object(); 占用多少内存?
数组的大小如何计算?
验证
添加Maven依赖
org.openjdk.jol jol-core 0.9
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 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字符串为例且内容互不相同字符串为例(假设字符串中的字符均为字母、数字、下划线)。
可以用来计算内存使用量 。
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://sefzo.baihongyu.com/