美团追魂连环7问

经过前几天美团一面的体验感觉,明显感觉到,美团面试官对于基础的痴迷和追问,不是你看点面经什么的就可以糊弄过去的,与面试管的畅聊也激发了我想要进一步细致学习的兴趣。无意间看到马士兵老师的这个课程,写成博客记录一下
课程资源

关于Object o = new Object()

1:对象的创建过程(半初始化)

美团追魂连环7问_第1张图片

2:DCL与volatile问题(指令重排)

DCL:Double Check Lock
是在单例模式中,我们在getInstance()方法中判断,如果instance为null,则创建单例对象。此时,如果两个对象同时调用该方法,则instance==null判断时,都为null,则会创建出两个示例对象,就不符合单例模式了,即出现了线程不安全。为了保证线程安全,我们需要使用synchronized,添加锁机制。若直接修饰getInstance()方法,如果方法里的一些操作不需要加锁,则会降低代码的并发性,即锁的粒度太大,我们这里只需要在创建实例对象的时候,添加锁就可保证线程安全。
第一次判断instance为null,如果为空,就申请锁,创建对象。
(如果此时,线程A获得锁,进入创建实例对象,线程B等待,当A释放锁,B获得锁,此时instance已经不为null,但是如果我们没有进行二次判断,这时,就会再次创建新的实例对象,就不符合单例的场景了)
所以我们需要在申请到锁后,再次判断instance是否为null。
代码如下:

package singleton;
/*
 * (1)构造器私有化
 * (2)不自行创建,只定义一个静态变量
 * (3)提供一个静态方法,获取实例对象
 * Double Check Lock
 */
public class Singleton5 {
	//要不要加volatile
	//volatile:作用1 线程可见性   作用2 禁止指令重排序
    private static volatile Singleton5 instance;
    private Singleton5() {}
//为了线程安全,可以对getInstanc()上锁,但是锁的范围(粒度)太大了,如果方法里的一些操作不需要加锁。
	public static Singleton5 getInstance(){
		//添加一个同步锁,保证线程安全
		//为了保证线程安全得同时,提供效率,把对instance==null得判断放在锁得外面
		if(instance==null) {
			synchronized (Singleton5.class) {
				//双重检查
				if(instance==null) {
//测试当两个线程,先后进入到这里,休眠,那么就会创建两个实例,所以这个例子存在线程安全问题
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					instance = new Singleton5();
				}	
			}
		}
		return instance;
	}	
}

再思考一个问题:
DCL单例到底需不需要volatile-----需要
volatile
作用1 :线程可见性
作用2 : 禁止指令重排序
指令重排序的意思就说,若指令1读内存很慢,cpu不会一直等待,而是先去执行与指令1无相关性的指令2,这样按理指令的执行顺序是1->2,而实际是2->1.这就是指令重排序。
美团追魂连环7问_第2张图片
如果不用volatile 禁止指令重排序(底层实现是会对这块区域上下都加内存屏障),那么当线程1半初始化后,发生指令重排,先执行astore使得t去指向半初始化的对象,此时,线程2在进行排队判断的时候,就会去使用这个半初始化的对象,例如,成员变量,本应该是8,这时候是0.,从而出现错误。美团追魂连环7问_第3张图片

3:对象在内存中的存储布局(对象与数组的存储不同)

美团追魂连环7问_第4张图片
mark word+classpointer是对象头
mark word(8字节):存放有锁状态,分代年龄,hashcode等
classpointer(4字节):例如T t = new T();那么classpointer会指向T.class
instance data(?字节):放的是该类型实例的成员变量,若成员变量是一个int 一个string ,则为(4+4字节),此时padding 补4
padding(?字节):若前3项占用的空间不够8整除,会用来补齐(cpu总线在读取数据内容的时候,是希望读取的内容和它对应的位数是一一对应的,才是最好。对于64位的计算机,它的寄存器也是64位,就希望我们读到数据的大小能刚好被8整除,可以提高效率)

4:对象头具体包括什么(markword classpointer)synchronized锁信息

包括:锁信息,GC信息,hashcode
美团追魂连环7问_第5张图片

5:对象怎么定位(直接间接)

直接:直接指针
间接:句柄方式
美团追魂连环7问_第6张图片
句柄方式:
优点:对象小,垃圾回收时不用频繁改动t
缺点:两次访问

6:对象怎么分配(栈上-线程本地-Eden -Old)

美团追魂连环7问_第7张图片
栈上分配原则:没有逸出(别的方法不用这个对象),标量替换(直接存两个成员变量)
如果在栈上分配,结束时,直接出栈,不需要GC的介入,效率高
TLAB(Threadlocal Allocation Buffer 线程本地分配缓冲区)

7: Object o = new Object()在内存中占多少字节

主要是要看是否开启压缩:
压缩有两种:1 压缩classpointer 2:压缩普通对象指针
o为普通对象指针(OOPS):4个字节
16+4 = 20个字节

你可能感兴趣的:(#,java,面试笔记)