面试题整理(5)
每天五道面试题!只记录答案,不标出源码,有什么不对的地方欢迎评论,共同进步。
1. 单例模式的双重检查锁为什么要加volatile?
因为对象创建的过程不是一个原子性操作。
对象创建过程分为三个步骤:1、申请内存。2、执行构造方法,给成员变量赋值。3、将创建的对象地址给引用变量。
其中步骤2,3是可能发生指令重排序的,如果发生3在2之前,就会出现检查发现引用变量不为null,之后直接返回实例的情况,这种情况下的实例中的成员变量只进行了初始化,还没有执行构造方法赋值,可能出现空指针异常。
加上volatile可以防止指令重排序,从而到达双重检查锁单例模式的安全实现。
2. 说一说对象在内存中的存储布局
对于HotSpot(JVM的一种实现,是使用范围最广的JVM),分为四个部分:
1、对象头(markword),其中包含hashcode、锁的信息(synchronized锁升级信息)、GC信息(对象的年龄,4位,所以最大年龄就是2^4-1 = 15),如果是数组,会记录数组的长度。
2、类型指针(class pointer),指向类型,比如一个cat对象会指向Cat.class。
3、实例数据(instance data),对象中的成员变量。
4、对齐(padding),如果前三者加起来不是八字节的倍数,会补齐到八字节的倍数。
3. 对象是如何定位的
1、直接指针,访问快,是HotSpot的实现,引用指向堆中的对象,对象中存着类型信息。比如我们创建一个对象Cat cat = new Cat();
,cat这个引用指向堆中cat对象,这个对象中又存着指向Cat.class的指针,JDK1.8之后,类型信息存放在元数据区。
2、句柄池(指针池),也叫间接指针,节省内存空间,其他JVM的实现,引用指向一个对象池,对象池分两部分,这两部分不一定是连在一起的,一部分指向对象,另一部分指向类型信息。
4. 对象的分配过程
按以下五个步骤分配:
1、栈上分配(默认打开),需要做逃逸分析,如果没有逃逸会进行一系列优化,比如栈上分配、同步消除(比如StringBuffer只在当前方法内使用,就不会加锁同步)、标量替换(比如一个对象只有int,long这种基本数据类型的成员变量,会将该对象成员变量分解若干个被这个方法使用的成员变量所代替。这些代替的成员变量在栈帧或寄存器上分配空间)
2、TLAB(Thread Local Allocation Buffer),每个线程都有一块私有空间,占用Eden区1%,一些很小的对象会分配到这里。
3、如果对象很大,会直接分配到Old区。
4、分配到Eden区。
5、到一定年龄的对象会转到Old区。
5. Object o = new Object()占多少个字节(HotSpot)?
1、对new Object(),markword占8字节,class pointer占4字节(开启压缩情况下),instance data占0字节(因为里面没有成员变量),padding占4字节(因为8+4不是8的倍数,要补齐到8的倍数,所以占4字节)。
2、对Object o,4字节(开压缩情况下)。
所以整个句子来说,占20字节。
PS:JVM参数可以指定压缩或非压缩,默认是开启压缩的,但是目前一些server大于某个数就不开压缩了。不开压缩的话class loader占8字节(padding会变成0字节),Object o占8字节,总共就是24字节。