JVM (九) 指针压缩原理-计算对象大小
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:热点代码缓冲区