一个int类型4占4个字节的内存,一个byte一个字节。但是他们的封装类型Integer,Byte对象内存损耗还是一样的吗?并不是,而且差距十分大。
HotSpot虚拟机中,一个普通的Java对象由3部分构成
- 对象头
- 类内定义的实例数据
- 内存对齐
2不必多说,Java对象不存定义好的实例字段存啥。
对象头又分两部分,Mark Word和类型指针。Mark Word存储对象运行时数据,如对象的hashcode,对象的分代年龄,锁状态等等。对象的分代年龄完全是为了JVM的Young GC设计的,每经历一次Young GC且存活下来的新生代对象,对象的分代年龄变会加一。直到这个字段增长到阈值,这个对象便会晋升到老年代(tenured generation)。这个字段给的是4个bit,也就是说一个java对象最多经历16次Young
GC并且没被回收,那么它将进入老年代。hashcode占25bit,锁标志位占2bit,一个bit固定为0.也就是说整个Mark Word占4个字节。
每种对象实例在方法区都会与一个Class对象与之相对应。对象头的类型指针便是用来定位它的类的元数据。为何要存这个数据?原因之一便是,方法区也要GC(确切的说是Full GC),如果没有一个类实例指向方法区的Class对象,那么这个Class对象便会被卸载回收。不知道大家有没有经历过java.lang.OutOfMemoryError: PermGen space 这样的错误。如果类一直在加载,永久代必然会被打爆。32位的JDK,类类型指针占4个字节。
现在说说内存对齐。内存对齐可不是jvm的专利,C/C ++ 下,类对象也是会内存对齐的。HotSpot要求对象的起止地址必须是8的倍数,由于对象头正好8个字节,因此当类字段没对齐时,就需要通过填充来对齐。为何要对齐呢?数据不是占内存越小越好吗?这个时候就得提提我们的cpu高速缓存了(L1 cache, L2 cache),这些缓存都是64字节一行。
最后来回答开头的问题,Integer大小是int的4倍(用了额外的4Byte补齐),Byte是byte的16倍(用了额外的7Byte补齐)。你算出来了吗?
两年前看《effective java》,有一个章节提到尽量用8大原生类型,那个时候对java对象内存布局完全没概念。现在回想起来,这个suggesstion是很有建设意义的。能节约很多堆内存啊,尤其是在从DB读取大量字段的时候。