对象占用字节数
考察知识点:对象的数据信息
-
对象包括:
-
对象头:
-
mark word
包含hashcode、GC年龄、锁信息等。
-
klass:指向堆中类的Class对象
-
(如果是数组的话)数组长度
-
-
实例数据:
-
对齐填充:填充以使得CPU可以更好地读取数据,如果未填充,那么可能需要多次读取,而且可能造成指令无法进行原子性的执行。
-
-
Object占用内存分析
markword由c++实现,默认是8字节。
如果我们使用的是64位虚拟机,jvm默认开启指针压缩,那么klass由于是使用的堆中的对象,那么kalss占用4字节。
总共8 + 4 = 12字节 (不为8的倍数) + 4字节填充 = 16字节
如果我们关闭指针压缩,那么klass占用8字节。
总共8 + 8 = 16,无需填充
-
布尔占用1字节,不过会有补齐。其他中规中矩
泛型
使程序经过编译期检查,更容易发现向下转型错误。我们知道,在对象进行实例化的时候,步骤是
检查类加载 - 分配内存- 赋值零值 - 设置对象头 - 调用构造方法
构造方法调用时,会调用父类的构造方法。那么对象的内存如下。
父类:
实例数据
方法
子类:实例数据
方法
我们的代码如果是
父类 a = new 子类()
其实在初始化子类的时候会调用父类的client和init,那么内存已经分配好了。
向下转型是可以的,不会有问题。
如果是
父类 a = new 父类();
向下转型会抛出错误,没有子类的属性和方法。
为了能在编译期检查出这种错误,提供了泛型。
泛型会帮助我们检查类型。
泛型实际编译后的代码是类型擦除的。
带来的问题:
父类如果是泛型类,子类重写父类时,如果指定了泛型类型,由于父类的类型擦除,导致的结果是会多出现几个方法,相当于实现了重载,而不是重写,java编译时会帮我们加上相关方法的调用来解决这个问题。例如:
public class TTest<T> {
public T v;
public T getV() {
return v;
}
public void setV(T v) {
this.v = v;
}
}
class S extends TTest<String> {
@Override
public String getV() {
return super.getV();
}
@Override
public void setV(String v) {
super.setV(v);
}
}
// 实际编译后
class S extends TTest<String> {
public getV()Ljava/lang/String;
L0
LINENUMBER 22 L0
ALOAD 0
INVOKESPECIAL base/TTest.getV ()Ljava/lang/Object;
CHECKCAST java/lang/String
ARETURN
// access flags 0x1
public setV(Ljava/lang/String;)V
LINENUMBER 27 L0
ALOAD 0
ALOAD 1
INVOKESPECIAL base/TTest.setV (Ljava/lang/Object;)V
// 多出的方法,直接调用本类的String参数的S.setV (Ljava/lang/String;)
public synthetic bridge setV(Ljava/lang/Object;)V
L0
LINENUMBER 19 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/String
INVOKEVIRTUAL base/S.setV (Ljava/lang/String;)V
RETURN
// 多出的方法
public synthetic bridge getV()Ljava/lang/Object;
L0
LINENUMBER 19 L0
ALOAD 0
INVOKEVIRTUAL base/S.getV ()Ljava/lang/String;
ARETURN
}
而且还有问题就是 即使Apple
和Fruit
是有继承关系的,List<Apple>
和 List<Fruit>
也没有继承关系。你不能用两个一样的杯子,一个装了苹果,一个装了水果,你就说这两个杯子是有继承关系的,这好像说不通。
所以为了匹配这种的。加入了通配符<?>、<extends>、<super>
。用来实现各种要求。