内存优化篇-String/char[]/byte[]的选择

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());
    }

}





内存优化篇-String/char[]/byte[]的选择

byte[] 和char[]的 object header为16个字节是因为有4个字节的数组长度。



String /  char[]   / byte[] 内存大小计算


    String a = new String("abc");
    String b = new String("abcd");
    String c = new String("abc");



内存优化篇-String/char[]/byte[]的选择

第一行占用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/char[]/byte[]的选择

 

用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://*.com/questions/26357186/what-is-in-java-object-header/26416983

http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

上一篇:文章相似度与聚类(一种简单高效的算法)


下一篇:什么是服务器?