内存布局分3个部分:
1.对象头
- 第一部分是对象自身运行数据Mark Word(hashcode、GC分代年龄、锁状态)。这是一个非固定数据结构,以便在极小的空间储存尽量多的信息。
- 另一部分是类型指针Class MetaData Adress,指向类的元数据,确定类型。
- 如果是数组还有一个array length记录数组长度。
2.实例数据
储存真正信息,即代码中定义的各种类型的字段,包括继承来的父类属性。
3.对齐填充
hotspot要求对象的起始地址必须是8字节的倍数,所以每个对象如果不满足,则要填充。猜测这样还是为了满足CPU cache line。充分利用CPU缓存。
对象访问
采用直接引用,变量在栈中直接储存对象的堆内存地址。优点是快,类似c语言。缺点是随着GC的内存移动,要更改对象的内存地址。
对象访问创建流程
- 执行类加载,初始化类变量信息
- 分配内存区域
- 初始化变量初始值
- 初始化对象头信息
- 执行构造器
内存分配
使用带内存整理功能的GC时
分配多大内存即移动内存指针与分配大小相同的距离
使用不带内存整理的GC时
KVM维护一个内存快列表,分配内存时找出一块足够大的内存区域进行分配。无法找到时触发GC。
内存分配的并发问题
- 采用CAS重试保证内存分配操作的原子性
- 在堆中为每个线程分配一小块内存,内存先在TLAB上分配,分配不足时再从堆中分配新的TLAB,才需要CAS。经测试性能快一倍。
指令重拍问题
CPU充分利用多核能力,会将没有依赖关系的代码乱序执行。执行完成后再进行重组,保证顺序和一致性
在new对象时,初始化对象和设置引用指向的内存地址会发生指令重排序。所以多线程时再对象初始化之前执行了变量访问对象会出现问题。
解决方法是加上volitile ,他会给变量加上lock锁。来禁用指令重排。