java对象在堆内存中的存储布局可以分为三个部分:对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)。
对象头对象头信息又可以分为两类信息。第一类主要是用于记录对象自身的运行时数据:hashcode、gc分代年龄、锁状态标志、线程持有锁、偏向锁信息等,这些信息也被称为Mark word。需要注意的是Mark word是个动态的数据结构,会根据锁状态标志不同动态的存储不同的数据(也就是上图中红色区域的部分,不是并存的,而是只存在其中一种)。在32位的jvm中,锁状态标志位占用2bit,不同状态下Mark word存储的数据如下表。
标志位 | 状态描述 | 存储内容 |
01 | 未锁定 | hashcoe 、gc分代年龄信息 |
01 | 可偏向 | 偏向线程id、偏向时间、gc分代年龄 |
00 | 轻量级锁定 | 指向锁记录的指针 |
10 | 重量级锁定(膨胀锁定) | 指向重量级锁的指针 |
11 | GC标记 | 空,(即将回收,也不在乎那信息了) |
这里可能有点疑惑,为啥标志位01可以表示位两个状态(未锁定、可偏向)?如果对象被锁定了,但是代码中又用到hashcode,但是对象头里已经没有该数据这有该怎么处理呢?其实这些疑虑,jvm设计师大神都考虑到。其实偏向锁不能算是锁,只不过是个调优手段,因为大部分情况下,对象经常被同一个线程频繁访问。所以当代码执行到synchronized进入同步块的时候,如果同步对象没有被锁定,jvm会现在线程的栈帧中建立一个锁记录的空间,将同步对象的Mark word拷贝一份,在记录下此mark word拥有的的对象地址(owner)。然后就尝试修改同步对象的Mark word信息。由于现在没其他线程竞争,这时的偏向锁没调用系统底层的加锁机制就相当于无锁的状态,并且下次该线程在此访问此同步对象时只比对下mark word中的线程id是否自己就完成了,效率极高,所以就用了相同的标志位01。
其实其他的锁定也是在线程的栈帧中建立一个锁记录的空间基础处理的(这里不描述锁的膨胀升级了),当锁释放时,同步对象上的Mark word从备份中还原回去。
对象头的另外一部分是类型指针,jvm通过这个指针确定对象是哪个类的实例。如果对象是个数组则对象头中还要开辟空间记录数组的长度。
实例数据实例数据是对象真正存储的有效信息,就是代码里定义的各种类型的字段内容,包括了从父类继承下来的字段。
对齐填充这个作用主要是占位填充,使对象的占用的内存大小是8byte的整数倍(jvm就是这么设计对象内存的,主要是为了管理对象内存分配,回收)。当然这个对齐填充不一定是必然存在的,对象头已经设计成了8byte的整数倍(32位-1倍,64位-2倍),如果实例数据部的空间占用的已经是8byte整数倍,该对象就不在进行对齐填充了。
查看对象大小目前有开源的工具可以查看一个对象占用的空间大小。例如jol-core包就可以查看对象大小。
org.openjdk.jol
jol-core
0.9
public class JolTest {
public static void main(String[] args) {
A a = new A();
a.makeDataToMap();
ClassLayout aLayout = ClassLayout.parseInstance(a);
System.out.println(aLayout.toPrintable());
System.out.println("#######################");
B b = new B();
b.makeDataToMap();
b.updateMap();
ClassLayout bLayout = ClassLayout.parseInstance(b);
System.out.println(bLayout.toPrintable());
}
}
//A
public class A {
private int i = 19;
Boolean fb = true;
Map map = new LinkedHashMap();
public void makeDataToMap(){
map.put("din","ddddd");
map.put("double",90d);
}
}
//B
public class B extends A{
private String name ="b";
private int [] ldata ={89, 9,97,45};
public void updateMap(){
map.put("ldata",ldata);
}
}
执行结果如下:
tree.node.A 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int A.i 19
16 4 java.lang.Boolean A.fb true
20 4 java.util.Map A.map (object)
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
#######################
tree.node.B 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) 17 cd 00 f8 (00010111 11001101 00000000 11111000) (-134165225)
12 4 int A.i 19
16 4 java.lang.Boolean A.fb true
20 4 java.util.Map A.map (object)
24 4 java.lang.String B.name (object)
28 4 int[] B.ldata [89, 9, 97, 45]
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)