跳至主要內容

JVM (九) 指针压缩原理-计算对象大小

安图新大约 4 分钟

JVM (九) 指针压缩原理-计算对象大小

前言:

Oop-Klass 体系回顾,在 JVM 第一篇中讲过了这部分内容,今天的内容也涉及,回顾一下。

ooPDesc

---------MarkOopDesc:存放锁的信息,分代年龄等等

---------InstanceOopDesc:非数组对象

---------arrayOopDesc:数组对象

-----typeArrayOopDesc:基本数据类型数组,对应的有个存放基本数据类型数组元信息的 TypeArrayKlass。

-----objArrayOopDesc:引用数据类型数组,对应有个 objArrayKlass 存放引用类型的元信息。

我们这里再举个例子,通过 HSDB 查看在 JVM 中的对象。

 
 

启动 HSDB,在 jdk1.8/lib 下执行:java -cp sa-jdi.jar sun.jvm.hotspot.HSDB

选择 main 线程,打开 stack memroy

第一个就是我们定义的 Position 对象的地址,第二个是字节数组,第三个是 char 数组。

 
 

我们 Inspector 查看 Position 对象。第一部分就是 Oop,在对象里面有元数据类型的 InstanceKlass。

这里有个重要的点就是:_layout_helper 参数。

1:如果是非数组对象,这个大小指的就是类生成的对象的大小。

2:如果是数组对象,是个负值。

3:等于 0 的话,?

 
 

内存布局

我之前写过一篇博客里面提到了这部分内容:https://blog.csdn.net/A7_A8_A9/article/details/105730007?spm=1001.2014.3001.5501

 
 
 
 
 
 

补充:针对上面的内存布局,这里有几点需要说明。

1:对象头中类型指针-class pointer 它是指向 instanceklass 在方法区的地址,如果开启指针压缩的情况下是占 4B,如果不开启是占 8B。

2:如果对象不是数组,对象头中的数组长度占 0B。是数组的话占 4 字节,因此数组的长度最大为 2 的 32 次方-1.

3:实例数据中 char 类型数据在 c++中是用 short 表示的,所以占两个字节。

4:实例数据中的引用数据类型,如果开启指针压缩占 4B,不开启指针压缩占 8B。

计算对象大小

指针压缩从 jdk1.6 之后是默认开启的。可以通过参数控制。

-XX:+UseCompressedOops/-XX:-UseCompressedOops

可以引用:这个依赖来打印对象布局大小。

<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.10</version>
        </dependency>

1:没有实例数据的对象

测试代码:

public class TestClass {
    public static void main(String[] args) {
        TestClass tes1= new TestClass();
        System.out.println(ClassLayout.parseInstance(tes1).toPrintable());
    }
}

结果:一共占 16 字节。

 
 

我们算下,这个 16 是怎么来的。其实很简单就是:对象头 8B+类型指针(压缩)4B+数组长度 0B+实例数据 0B+4B(对齐填充)=16B

不开启指针压缩就是:对象头 8B+类型指针(不开启指针压缩)8B+数组长度 0B+实例数据 0B=16B 没有了对齐填充也是 16B

2:有实例数据

我们给 TestClass 加两个实例属性。

public class TestClass {
    int i=2;
    String name="阿三";
    public static void main(String[] args) {
        TestClass tes1= new TestClass();
        System.out.println(ClassLayout.parseInstance(tes1).toPrintable());
    }
}

开启指针压缩:

Mark Word 8B+类型指针 4B(压缩)+数组长度 0B+实例数据 int 4B+引用数据类型 4B(压缩)+4B(对齐填充)=24B

我们看下运行结果:

 
 

关闭指针压缩:

对象头 8B+类型指针 8B(压缩)+数组长度 0B+实例数据 int 4B+引用数据类型 8B(压缩)+4B(对齐填充)=32B

运行结果:

 
 

指针压缩

开启指针压缩是为了节省内存,寻址效率有些提高。

指针压缩的原理:

假如分配的内存从 0 开始且顺序存储,三个对象分别: test1=16B test2=24B test3=32B, 三个地址分别:test1=00000, test2=16(十进制) /10000(二进制),test3=40(十进制) 101000(二进制)。

我们都知道 java 中的对象都是 8 字节对齐的,8 字节对齐有一个特点就是 1 000,发现了吗 所有对象的指针后三位总是 0。这就是指针压缩的点。

压缩原理就是两句话:

1:存储的时候,后三位抹除 0.

就变成:test1=00,test2=10

2:使用的时候,后三位补 0.

它的指针不再表示对象在内存中的精确位置,而是表示 偏移量 。这意味着 32 位的指针可以引用 40 亿个 对象 , 而不是 40 亿个字节。最终, 也就是说堆内存增长到 32 GB 的物理内存,也可以用 32 位的指针表示。

使用 HSDB 查看指针压缩现象:

示例代码:

public class TestClass {
    public static void main(String[] args) {
        Position position = new Position(1, 2, 3);
        while(true);
    }
}

关闭指针压缩:

 
 

对象地址:

 
 

打开指针压缩:

 
 

对象大小明显变小了,而且 klass 属性也不一样了。

这是因为在 OopDesc 中,指针压缩和不压缩 klass 存储在不同的地方。

 
 
 
 

JVM 调优基础知识

1:上线前对 JVM 进行预估调优

2:上线后小规模调优

3:OOM,full GC 频繁调优

主要调什么?

1:方法区

2:虚拟机栈

3:堆区

4:热点代码缓冲区

上次编辑于:
贡献者: Andy