问题1:解释一下对象的创建过程(半初始化)。
申请一块内存,给它赋值默认值(8大类型都有自己的默认值,此时为半初始化状态),然后调用构造方法,赋初始值,然后建立关联
线程上锁
需要两遍检查,DCL= double check lock,开始的时候判断是否为空,加锁,进去后再判断实例是否为空,然后再进行执行,检查两遍。
Volatile :保持线程可见性,禁止指令重排序(谁先请求的,就先返回谁,然后再进行下一个指令。),
问题2:DCL单例到底需不需要volatile?(指令重排)
如果没有禁止指令重排,第一个线程来了会进行版初始化状态,让t不为空,第二个线程来了判断t不为空,直接使用半初始化状态的值,出现不同的异常状态。因此要加上volatile,禁止指令重排序(对内存区域加了volatile,对这个内存限制)。[订单更新的时候会有更新]
问题3:对象在内存中的存储布局(对象与数组的存储不同)
普通对象和数组不同。
普通对象有四项:占用16个字节
markword(标记用,放锁状态年龄 hashcode等)、 8个字节
类型指针classpointer(指向属于哪个类型 a.class)、4个字节
实例数据instance data(存放实例数据int a = new int[0],存放a)、
对齐padding(前面三个内容的位数不是8的倍数(读一块内容的时候,整块读最快),则补上) 4个字节,
JOL能将能将对象打印出来,打印布局。
数组对象有5项:
markword(标记用,放锁状态年龄 hashcode等)、 8个字节
类型指针classpointer(指向属于哪个类型 a.class)、4个字节
数组长度
实例数据instance data(存放实例数据int a = new int[0],存放a)、
对齐padding(前面三个内容的位数不是8的倍数(读一块内容的时候,整块读最快),则补上) 4个字节,
问题4:对象头具体包括什么(markword、 classpointer、synchronized锁信息)
最主要的内容:锁的信息。有一个锁升级的过程。
New--- 偏向锁——自旋锁(无锁 lock-free 轻量级锁)— 重量级锁
偏向锁:贴标签
自旋锁:取消偏向锁,多个线程进行CAS竞争(先读出来,然后修改,再回去对比一下,如果值还是原来的,则贴上标签,加锁成功。), ABA问题,加个版本。自旋次数超过ncpu内存的一半,在jvm中进行抢占,则升级为重量级锁。
重量级锁:将线程放到队列里面,按照顺序执行
问题5:对象怎么定位(直接、间接)
在JVM中怎么指向对象。
1.句柄两次引用,效率低,唯一好处:垃圾回收的比较方便。
T在堆中通过实例数据指针倒找对象,通过类型数据指针在方法区找到t.class。
2.直接指针,平时常用
T指向堆中的类型数据指针,在方法区中找到t.class.
问题6:对象怎么分配(栈上-线程本地-Eden-Old)
Start — new (优先在栈上分配,栈上分配的好处:方法没了之后,栈及没有了,不需要对栈内存进行gc回收。需要满足1.逃逸分析,2.标量替换。)—- 栈上空间够用,直接end,如果不够用,如果很大,看看老年代,可能会fullgec;如果不大,线程本次分配TLAB,年轻代分配里面分配内存,
栈上比堆上快一倍。(java 优化,参照c语言的struct,struck在栈上分配)
1.栈上分配:不会有逸出,没有别的线程访问(没有别的方法使用)。直接使用成员两边替换这个对象。满足以上两个条件则可以进行栈上分配。
e.g. User对象,有两个成员变量 id 和name,小内容的东西直接放在栈上,别的方法不会调用这个user对象;直接使用id和name代替user对象,直接放到栈上。
Gc回收一次,年龄加一,到达一定年龄则进行回收。age是4位,最大表示15.
问题7:object o = new object() 在内存中占用多少字节
普通对象指针 o 占用4个字节,正常占用8个字节,但是开启了压缩,所以是4个。
Object对象占用:16字节,如果有压缩的话,padding是4,如果没有压缩的话,padding是0,指针占用8个字节了。默认压缩。
4字节寻址的最大空间:指向的最大数字2**4*8-1,32g
堆内存超过一定值,访问不到所有对象,压缩自动不起作用了,就算你自己扩展内存为48个g,结果发现对象跟32g一样,内存自动膨胀了。