JAVA对象头的指针压缩

6 篇文章 0 订阅

JAVA对象头的指针压缩

对象在JVM中的内存布局

在 Hotspot 虚拟机中,对象的内存布局主要由 3 部分组成:

  1. 对象头(Header):包含了对象运行时数据Mark Word,Klass Pointer、数组长度(数组对象才会有)。

    • Mark Word:包含对象运行时的信息,包括哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
    • 类型指针(Klass Pointer):即对象指向它的类型元数据的指针。通过直接指针访问对象才需要在对象上存储类型指针,通过句柄访问对象时不需要此指针。Hotspot采用的是直接指针访问对象的方式,所以这类虚拟机中的对象上存储了类型指针。
    • 数组长度:数组对象才会有。
      在这里插入图片描述
      在这里插入图片描述
  2. 实例数据(Instance Data):对象存储的真正有效数据,即当前类型的字段和父类继承的字段

  3. 对应填充(Padding):不一定存在,主要用于占位符。虚拟机中任何对象的大小都是8字节的整数倍,如果对象所占空间不是8字节的整数倍,会进行补充对齐。

对象的访问定位

在对象创建之后,JAVA程序会通过JAVA栈上的reference数据来操作堆上的具体对象。对象的访问方式主要由虚拟机自主实现,主流的有两种方式:

  • 通过句柄访问对象

    JAVA堆中会有一块内存作为句柄池,reference中存储的是对象的句柄地址,在句柄中包含了对象的实例数据和对象的类型数据的地址。

    优势:reference中存储的是稳定的句柄地址,在对象发生移动的时候(比如在垃圾回收的时候会移动对象),只改变了句柄中对象实例数据的地址,而reference不需要修改。

    // 访问图解

  • 通过直接直接访问对象

    JAVA堆中的对象实例数据中存储着对象类型数据的地址,reference中存储的是对象的地址。

    优势:访问速度快,在访问对象的时候比句柄方式少一次指针定位的时间开销,由于对象访问比较频繁,那这个节约的开销积累下来就很可观了。

    // 访问图解

压缩实验

实验步骤

  • 使用的是64位OS

  • 引入查看对象头布局的工具

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>
package org.donny;

import org.openjdk.jol.info.ClassLayout;

/**
 * @author 1792998761@qq.com
 * @description 借助openjdk的jol工具, 查看对象内存的占用情况
 * oop(ordinary object pointer)--对象指针
 * -XX:+UseCompressedOops 默认开启
 * -XX:+UseCompressedClassPointers  默认开启对象头里面的类型指针压缩
 * @date 2023/6/5
 */
public class TestObjectHeader {
    public static void main(String[] args) {

        System.out.println("============Object对象===========");
        ClassLayout layout = ClassLayout.parseInstance(new Object());
        System.out.println(layout.toPrintable());

        System.out.println("============int类型数组对象===========");
        ClassLayout layout1 = ClassLayout.parseInstance(new int[]{});
        System.out.println(layout1.toPrintable());

        System.out.println("============复合类型对象===========");
        ClassLayout layout2 = ClassLayout.parseInstance(new CompositeObjectsTest());
        System.out.println(layout2.toPrintable());
    }

    public static class CompositeObjectsTest {
        //8B mark word
        //4B Klass Pointer   如果关闭压缩-XX:-UseCompressedClassPointers或-XX:-UseCompressedOops,则占用8B
        int id;        //4B
        String name;   //4B  如果关闭压缩-XX:-UseCompressedOops,则占用8B
        byte b;        //1B
        Object o;      //4B  如果关闭压缩-XX:-UseCompressedOops,则占用8B
    }
}

在这里插入图片描述

压缩策略组合

VM的配置项

  • UseCompressedOops:压缩当前对象实例数据中的 Klass Pointer 指针
  • UseCompressedClassPointers:压缩当前对象的对象头中 Klass Pointer 指针

对象指针压缩+对象头的类型指针压缩

JDK1.8之后默认开启这两个压缩

VM options: -XX:+UseCompressedOops -XX:+UseCompressedClassPointers

============Object对象===========
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

============int类型对象===========
[I object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0xf800016d
 12   4        (array length)            0
 16   0    int [I.<elements>             N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

============复合类型对象===========
org.donny.TestObjectHeader$CompositeObjectsTest object internals:
OFF  SZ               TYPE DESCRIPTION                 VALUE
  0   8                    (object header: mark)       0x0000000000000005 (biasable; age: 0)
  8   4                    (object header: class)      0xf800f161
 12   4                int CompositeObjectsTest.id     0
 16   1               byte CompositeObjectsTest.b      0
 17   3                    (alignment/padding gap)     
 20   4   java.lang.String CompositeObjectsTest.name   null
 24   4   java.lang.Object CompositeObjectsTest.o      null
 28   4                    (object alignment gap)      
Instance size: 32 bytes
Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

对象指针不压缩+对象头的类型指针压缩

VM options:-XX:-UseCompressedOops -XX:+UseCompressedClassPointers

============Object对象===========
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8        (object header: class)    0x0000012a6a481c00
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

============int类型对象===========
[I object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8        (object header: class)    0x0000012a6a480b68
 16   4        (array length)            0
 20   4        (alignment/padding gap)   
 24   0    int [I.<elements>             N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

============复合类型对象===========
org.donny.TestObjectHeader$CompositeObjectsTest object internals:
OFF  SZ               TYPE DESCRIPTION                 VALUE
  0   8                    (object header: mark)       0x0000000000000005 (biasable; age: 0)
  8   8                    (object header: class)      0x0000012a6aae3210
 16   4                int CompositeObjectsTest.id     0
 20   1               byte CompositeObjectsTest.b      0
 21   3                    (alignment/padding gap)     
 24   8   java.lang.String CompositeObjectsTest.name   null
 32   8   java.lang.Object CompositeObjectsTest.o      null
Instance size: 40 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total

对象指针压缩+对象头的类型指针不压缩

VM options: -XX:+UseCompressedOops -XX:-UseCompressedClassPointers

============Object对象===========
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8        (object header: class)    0x000002d380801c00
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

============int类型对象===========
[I object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8        (object header: class)    0x000002d380800b68
 16   4        (array length)            0
 20   4        (alignment/padding gap)   
 24   0    int [I.<elements>             N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

============复合类型对象===========
org.donny.TestObjectHeader$CompositeObjectsTest object internals:
OFF  SZ               TYPE DESCRIPTION                 VALUE
  0   8                    (object header: mark)       0x0000000000000005 (biasable; age: 0)
  8   8                    (object header: class)      0x000002d380e63210
 16   4                int CompositeObjectsTest.id     0
 20   1               byte CompositeObjectsTest.b      0
 21   3                    (alignment/padding gap)     
 24   4   java.lang.String CompositeObjectsTest.name   null
 28   4   java.lang.Object CompositeObjectsTest.o      null
Instance size: 32 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total

对象指针不压缩+对象头的类型指针不压缩

VM options: -XX:-UseCompressedOops -XX:-UseCompressedClassPointers

============Object对象===========
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8        (object header: class)    0x00000234d96d1c00
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

============int类型对象===========
[I object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   8        (object header: class)    0x00000234d96d0b68
 16   4        (array length)            0
 20   4        (alignment/padding gap)   
 24   0    int [I.<elements>             N/A
Instance size: 24 bytes
Space losses: 4 bytes internal + 0 bytes external = 4 bytes total

============复合类型对象===========
org.donny.TestObjectHeader$CompositeObjectsTest object internals:
OFF  SZ               TYPE DESCRIPTION                 VALUE
  0   8                    (object header: mark)       0x0000000000000001 (non-biasable; age: 0)
  8   8                    (object header: class)      0x00000234d9d33210
 16   4                int CompositeObjectsTest.id     0
 20   1               byte CompositeObjectsTest.b      0
 21   3                    (alignment/padding gap)     
 24   8   java.lang.String CompositeObjectsTest.name   null
 32   8   java.lang.Object CompositeObjectsTest.o      null
Instance size: 40 bytes
Space losses: 3 bytes internal + 0 bytes external = 3 bytes total

结果集解释

OFF代表offset
SZ代表size
VALUE代表的十六进制的表示值
alignment/padding gap代表不足8字节的整数倍,填充补齐项
类型开启指针压缩不开启指针压缩
Object16字节16字节
int数组16字节24字节
复合对象32字节40字节
压缩内容

可以发现通过指针压缩可以对以下数据进行压缩:

  1. 对象头信息:64位平台下,原生对象头大小为16字节,压缩后为12字节
  2. 对象的引用类型:64位平台下,引用类型本身大小为8字节,压缩后为4字节
  3. 对象数组类型:64位平台下,数组类型本身大小为24字节,压缩后16字节

主要是对象头里面的kclass指针,即指向方法区的类信息的指针,由8字节变为4字节。 还有就是引用类型指针也由8字节变为4字节

压缩后的影响

压缩后的好处:

  1. 减缓GC的压力: 即每个对象的大小都变小了,就不需要那么频繁的GC了。
  2. 增加CPU缓存的对象指针数量,同时降低CPU缓存的命中率。即CPU缓存本身的大小就小的多,如果采用八字节,CPU能缓存的oop(普通对象指针)肯定比四字节少。
指针压缩的实现

地址总线的根数决定了最大可用内存空间容量。每一个字节(B)的内存空间被视为一个地址单元,整个内存可以看作很多个地址单元组成的数组,每个地址单元有唯一确定的地址来标定,来区分不同的地址单元。地址总线的数量会变化,数据不一定准确可以查询服务器硬件参数确认。

地址总线数目内存上限
32位操作系统32 2 32 2^{32} 232=4GB
64位操作系统36或者46 2 36 2^{36} 236=64GB或者 2 46 2^{46} 246=64TB

一种理解:

当开启指针压缩后,KlassPointer的寻址极限是4 byte × 8 bit=32 bit,即KlassPointer可以存放2^32(=4G)个内存单元地址。

因为每个对象的长度一定是8的整数倍,所以KlassPointer每一数位存放的一定是8的整数倍的地址,即0/8/16/24/32/40/48/64……,也就是4G × 8 = 32G。当分配给JVM的内存空间大于32G时,KlassPointer将无法寻找大于32G的内存地址,因此设置的压缩指针将失效。

第二种理解:

JVM的实现方式是

不再保存所有引用,而是每隔8个字节保存一个引用。例如,原来保存每个引用0、1、2…,现在只保存0、8、16…。因此,指针压缩后,并不是所有引用都保存在堆中,而是以8个字节为间隔保存引用。

在实现上,堆中的引用其实还是按照0x0、0x1、0x2…进行存储。只不过当引用被存入64位的寄存器时,JVM将其左移3位(相当于末尾添加3个0),例如0x0、0x1、0x2…分别被转换为0x0、0x8、0x10。而当从寄存器读出时,JVM又可以右移3位,丢弃末尾的0。(oop在堆中是32位,在寄存器中是35位,2的35次方=32G。也就是说,使用32位,来达到35位oop所能引用的堆内存空间

JVM内存关键大小

  • 当堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址
  • 当堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址, 那这样的话内存占用较大,会增加GC压力等等

参考文章
https://www.cnblogs.com/xiaomaomao/p/17350075.html
https://blog.51cto.com/u_87851/6326662
https://artisan.blog.csdn.net/article/details/106958768
https://blog.csdn.net/lioncatch/article/details/105919666

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中,可以使用压缩算法对字符串进行压缩以减少其长度。常见的压缩算法包括Gzip、Deflate和Bzip2等。通过对字符串进行压缩,可以减少字符串占用的内存空间,并在网络传输或存储时节省带宽和存储空间。压缩字符串的方法可以是通过统计连续出现的相同字符的次数来实现。首先,定义两个指针i和j,其中i指向字符串的首个字符,j向前遍历直到访问到不同的字符时停止。此时,j - i便是首个字符的连续出现次数。然后,从下个字符开始,重复以上操作,直到遍历完成。最后,将字符以及出现的次数添加到新的字符串对象中。将压缩后的字符串与原始字符串进行比较,返回长度较短的那个。在Java中,可以使用StringBuilder来创建新的字符串对象,使用charAt()方法获取字符串中的字符,通过append()方法将字符和出现次数添加到新的字符串中。最终,通过调用toString()方法将StringBuilder对象转换为字符串。例如: ```java public String compressString(String S) { int i = 0, j = 0, ls = S.length(); StringBuilder res = new StringBuilder(); while (i < ls) { while (j < ls && S.charAt(i) == S.charAt(j)) { j++; } res.append(S.charAt(i)).append(j - i); i = j; } return res.length() < ls ? res.toString() : S; } ``` 以上是一个示例代码,可以通过调用compressString()方法来对字符串进行压缩。其中,参数S是待压缩的字符串,返回值是压缩后的字符串。在这个示例中,我们使用Gzip压缩算法对字符串进行压缩,得到压缩后的字节数组。然后,我们输出压缩前后的字符串长度,以便比较压缩效果。最后,我们使用Gzip压缩算法对压缩后的字节数组进行解压缩,得到解压缩后的字符串,并检查解压缩后的字符串是否与原始字符串一致。通过对字符串进行压缩,可以将字符串的长度变短。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

顧棟

若对你有帮助,望对作者鼓励一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值