内存优化篇-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()); } }
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
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
MSSQL-最佳实践-行级别安全解决方案
title: MSSQL-最佳实践-行级别安全解决方案 author: 风移 摘要 在SQL Server安全系列专题月报分享中,我们已经分享了:如何使用对称密钥实现SQL Server列加密技术、使用非对称密钥加密方式实现SQL Server列加密、使用混合密钥实现SQL Server列加密技术和列加密技术带来的查询性能问题以及相应解决方案四篇文章。本期月报我们分享使用SQL Server RLS(Row Level Security)行级别访问控制解决方案最佳实践。 问题引入 在很久以前我分享过一篇文章SQL Server使用视图做权限控制来实现行级别数据安全。今天我们把这个问题再次抛出来:不同用户访问同一张表,如何做到不同用户仅能访问属于自己及以下层级的数据。还是举例这个例子,比如:公司有CEO,Manger和普通的employee三
- 下一篇
使用自定义注解实现接口参数校验
1.前言 在接口的开发中,我们有时会想让某个接口只可以被特定的人(来源)请求,那么就需要在服务端对请求参数做校验. 这种情况我们可以使用interceptor来统一进行参数校验,但是如果很多个接口,有不同的的设定值,我们总不能写很多个interceptor,然后按照path逐一添加吧? 面对这种情况,我们可以选择自定义一个注解,由注解来告诉我们,这个接口允许的访问者是谁. 注:在本文的示例中,仅实现了对某一个字段的校验,安全性并不高,实际项目中,可以采用多字段加密的方式,来保证安全性,原理和文中是一样的. 2.java 注解介绍 Java Annotation是JDK5.0引入的一种注释机制。 Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。 通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。 Annotation可以像修饰符一样被使用,可以用于package、class、interface、constructor、method、member variable(成员变量)、parameter...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2整合Thymeleaf,官方推荐html解决方案