JVM调优基础篇-java对象大小计算

背景简化:最近由于项目需要,需要计算一下对象的大小,防止放开灰度后导致服务期频繁GC

读完这篇文章可以获得什么?

  • 对象的内存布局
  • 指针压缩的原理
  • 预估对象的大小
  • 对象是否只能在堆上分配

基础

1、对象的内存布局

一个Java对象在内存中包括对象头、实例数据和补齐填充3个部分
对象的内存布局
由于本文主要是讲述对象的大小计算,所以不会详细讲解每个部分的作用,有兴趣可以上网搜索一些相关文章阅读。

对象头
所有对象都会有的部分:
Mark Word
Mark Word 用于存储对象自身的运行时数据
如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID 、偏向时间戳等
这部分数据的长度在32 位和64 位的虚拟机中分别为32 bit 和64 bit 
Klass Pointer
用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。
在32位系统占4字节,在64位系统中占8字节;
64位机中开启指针压缩占4字节

注意是klass 而不是class许多文章都是class pointer可参考HotSpotGlossary

数组对象存在的部分:
Length
如果是数组对象,还有一个保存数组长度的空间,占4个字节
Padding
如果是数组对象且未开启指针压缩则还会存在一个padding用来对齐

这个部分很多文章都省略了但确实存在

Instance Data
  对象真正存储的有效的信息,程序代码中定义的各种的数据的类型
  如果有继承的关系,还有继承父类的字段。
  分配策略(参数FiedsAllocationStyle)影响java中定义的顺序,对相同宽度的字段总是被分配到一起,在这种情况下,父类定义的变量会出现在子类之前。CompactFields 为true (默认为true) 子类中较窄的变量也可能插入到父类变量中。 

HostSpot 的默认分配策略为

  • longs/doubles
  • ints
  • shorts/chars
  • bytes/booleans
  • oops(Ordinary Object Pointers)
数据类型分为基本数据类型和引用数据类型
基本数据类型
Type Bytes
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8
引用数据类型
在32位系统占4字节,在64位系统中占8字节
Padding
由于 HostSpot VM 的自动内存管理系统要求对象的起始地址必须是8字节的整数倍
换句话说对象的大小必须为8字节的整数倍,要是实例数据没有对齐,则需要进行对齐填充来补全
以8字节对齐还是16字节对齐可以配置

这部分没有特殊意义填充0值

2、指针压缩

64位的JVM支持 -XX:+UseCompressedOops  来开启指针压缩功能 1.6 后默认开启
启用CompressOops后会压缩的对象:
    1、每个Class的属性指针(静态成员变量)
    2、每个对象的属性指针
    3、普通对象数组的每个元素指针
当然,压缩也不是所有的指针都会压缩,对一些特殊类型的指针,JVM是不会优化的
例如指向PermGen的Class对象指针、本地变量、堆栈元素、入参、返回值和NULL指针不会被压缩。
指针压缩的实现原理
前提条件

java对象默认按8字节对齐
假设内存中只有三个对象 t1 = 16字节 t2 = 32字节 t3 = 24字节
再假设分配内存是从0开始分配 则三个对象的内存地址为

  • 第一个内存地址 0X00000
  • 第二个内存地址 0X10000
  • 第三个内存地址 0X30000
    这时候再想一下以8字节分配有什么特点?后三位永远都是0
结果

实现原理为存储的时候后三位0抹除 0X00 0X10 0X30``````使用的时候后三位补0
实际就是一个编码和解码的过程,针对指针压缩也有一些优化,如零基压缩,由于本文是希望尽可能简单的将压缩的实现原理,所以不再这篇文章赘述,感兴趣的可以搜一些相关文章查看

问题

一个oop所能表示的最大的内存空间是多少? 2的35次方 = 32G
为什么是 35呢? 开启指针压缩后 一个oop的大小是4字节 = 32 位 再加上取出后 后三位会补充0 所以是 32+3 =35
怎么样扩大oop的最大的内存空间呢?
改为16 /32 或者更大的 字节对齐 但是这样做的会导致空间的浪费没有必要

对象大小的计算

有了上边的基础,我们再来进行对象大小的计算

1、没有实例数据的对象

public class EmptyTuan {
    
    public static void main(String[] args) {
        //使用jol计算对象的大小(单位为字节):
        System.out.println(ClassLayout.parseClass(EmptyTuan.class).toPrintable());

        //使用jol查看对象的内存布局:
        System.out.println(ClassLayout.parseInstance(new EmptyTuan()).toPrintable());
    }
}
开启指针压缩
对象内存分布
org.learn.code.jvm.classsize.EmptyTuan object internals:
 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

16 = 8 (mark word) + 4 (klass Pointer) + 0 (Instance Data) + 4 (padding)
由于按8字节对齐所以浪费了4个字节

关闭指针压缩

-XX:-UseCompressedOops

对象内存分布
org.learn.code.jvm.classsize.EmptyTuan object internals:
 OFFSET  SIZE   TYPE DESCRIPTION   
  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值