栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

Java - 对象内存计算以及String类型的相关注意事项

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java - 对象内存计算以及String类型的相关注意事项

Java - 对象内存计算以及String类型的相关注意事项
  • 一. 基础知识复习
    • 1.1 对齐填充占用内存
    • 1.2 基础数据类型占用内存表
    • 1.3 指针压缩
  • 二. String类型所占内存
    • 2.1 String类型的易错点

一. 基础知识复习

我们知道,Java中,对象在内存中的内存布局分为三个部分:

  • 对象头。
  • 实例数据。
  • 对齐填充。

可以复习下相关知识深入理解Java虚拟机系列(一)–Java内存区域和内存溢出异常


而对象头又可以分为三个部分:

  1. Mark Word,64位操作系统下占8字节,32位系统下占用4字节。
  2. 类型指针:在开启指针压缩的状况下占 4 字节,未开启状况下占 8 字节,默认开启指针压缩,因此占用4字节。
  3. 数组长度(只有数组对象才会有,本篇文章都是普通对象为例)
1.1 对齐填充占用内存

然后说下对齐填充。需要注意的是:Java对象的大小默认按照8字节来对齐。即为8字节的整数倍大小。倘若对象大小不足,则由对齐填充部分来补充。

提问:为什么要进行8字节的对齐?

回答:

  1. CPU 进行内存访问时,一次寻址的指针大小是 8 字节,正好也是 L1 缓存行的大小。
  2. 如果不进行内存对齐,则可能出现跨缓存行的情况,即缓存行污染。 如图:

缓存污染是指操作系统将不常用的数据从内存移到缓存,降低了缓存效率的现象

解释:

  1. 我们主要访问obj1这个对象,CPU就将对应的L1缓存读取过来。里面包含了obj1和obj2两个对象。
  2. 在后续对obj1进行修改的时候。倘若CPU在访问obj2对象时。由于obj2所在的缓存行中数据被修改了。因此此时CPU必须将其重新加载到缓存行中。影响程序的执行效率。
  3. 倘若obj2有自己的L1 Cache空间。那么在修改obj1对象的时候,就不会对obj2产生影响。

因此,采用8字节的对齐填充,是一种用空间换时间的一种方案。

那么总的来说,一个对象所占的内存,记住2点即可:

  1. 对象内存 = 对象头 + 实例数据 + padding 填充。
  2. 对象内存为8字节的整数倍。
1.2 基础数据类型占用内存表
类型占用空间(B)
boolean1
byte1
short2
char2
int4
float4
long8
double8
1.3 指针压缩

上文中提到了类型指针的内存占用情况,在开启指针压缩功能的情况下,占用4个字节,否则是8个字节。这个功能在JDK1.6版本之后就开始支持了。JDK1.8里面则是默认开启状态的。

启用 CompressOops 后,会压缩的对象包括:

  • 对象的全局静态变量(即类属性)。
  • 对象头信息。
  • 对象的引用类型:64 位系统下,引用类型本身大小为 8 字节,压缩后为 4 字节。
二. String类型所占内存

我们以String为例,做一个小测试。首先我们引入一个pom依赖:


    org.openjdk.jol
    jol-core
    0.16

Java代码:

System.out.println(ClassLayout.parseInstance("a").toPrintable())

结果如下:


分析:

首先我们来看下object header,上图一共有2个,即对象头部分总共加起来消耗了12kb。

  1. 首先我的机器是64位的。使用java -version命令即可查看:

  2. 其次,由于是64位的机器,因此对象头中的Mark Word部分占用的8个字节大小。对应的是图中的object header: mark部分。而object header: class则指的是类型指针,占用4个字节大小。因此这里一共是12字节。

其次我们来看下这两个部分:

这里我们看下String类中包含了哪些成员变量:

public final class String implements java.io.Serializable, Comparable, CharSequence {
    
    private final char value[];

    
    private int hash; // Default to 0
}

分别对应了:

  1. char[] 数组。用来存储字符串的一个引用。64 位系统下,引用类型本身大小为 8 字节,压缩后为 4 字节。
  2. int类型的hash变量。根据1.2中基础数据类的内存占用表得知,int类型占用了4个字节。对得上。

到这里为止,总共的大小为12 + 4 + 4 = 20字节。但是其并不是8的整数倍。因此对齐填充会额外占用4个字节的大小,因此一个String类型的字符串占用了24个字节。


以防万一,我在拿一个自定义类当例子:

public class SizeObject {
    public int size;
    public double money;
    public byte[] bytes;
}

计算其所占内存:

System.out.println(ClassLayout.parseInstance(new SizeObject()).toPrintable());

结果如下:

  1. 对象头+类型指针,依旧是固定的8+4=12个字节。
  2. int类型占用4个字节,double占用8个字节,byte[]数组属于引用类型,4个字节。
  3. 到这里一共12 + 4 + 8 + 4 = 28个字节。然后并不是8的整数倍,通过对齐填充,再补4个字节。最终得到32字节。
2.1 String类型的易错点

首先,我们依旧用上述的例子:我们增长了这个字符串对象,我们看看它占用了多少的内存。

System.out.println(ClassLayout.parseInstance("adfadsfdsfdsafas").toPrintable());

结果如下:

可见,它还是24个字节大小。为什么我字符内容变长了,这个对象的占用内存还是24B呢?这要说到Java的内存数据结构了,这是我在深入理解Java虚拟机系列(一)–Java内存区域和内存溢出异常中贴出的图:

我们看到,有一个运行时常量池和字符串常量池。Java中对于String类型,在实例化字符串的时候做了对应的优化操作:

  1. 每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。
  2. 如果字符串不在常量池中,就会实例化该字符串,并将其放在常量池中。

文章提到过,String类型中的char[]数组保存的是这个字符串的引用地址,真正的实例对象则是在堆中另外开辟一块空间来存储的。 因此,无论我这个字符串的内容有多少,并不会改变char[]这个引用数组所占的内存。因此在计算String这个实例所占内存的时候,char[]占用的字节数永远是4个字节。

测试:

String a = "hello";
String b = "world";
String c= "helloworld";
String res = a + b;
String res2 = "hello" + "world";
System.out.println(c == res);
System.out.println(c == res2);
System.out.println(res == res2);

结果如下:

分析:

  1. 栈中开辟了一块空间引用(String类中的char[]数组),“hello”放入到常量池中,a指向它。
  2. 栈中又开辟了一块空间引用(String类中的char[]数组),“world”放入到常量池中,b指向它。
  3. 栈中又又开辟了一块空间引用(String类中的char[]数组),“helloworld”放入到常量池中,c指向它。
  4. String res = a + b;这段代码本质上调用的是StringBuilder().toString()方法,会返回一个新的String实例,因此此时会在堆中生成一个对象来保存。运行时执行。
  5. String res2 = "hello" + "world";由于“hello”和“world”都是常量池中的常量,当字符串由多个字符串常量拼接而成的时候,其本身也是字符串常量。
  6. c==res -->false:res为堆中的一个对象,和常量相比,必定为false。
  7. c==res2 -->true:因此res2在创建的时候,发现常量池中已经存在同样的字符串helloworld。返回对应的实例。因此两者本质是一个东西。
  8. res2==res -->false:res为堆中的一个对象,和常量相比,必定为false。同理第六点。
转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1036622.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号