java关于对象的几个面试题
1.请解释一下对象创建的过程
案例:
public class Test {
int m = 8;
public static void main(String[] args) {
Test t = new Test();
}
}
使用下图插件区查看字节码:
0 new #3 <com/java/Demo1/Test>//在内存中新开辟一块空间,并将成员变量全部设为默认值,此处将m设为0;
3 dup//复制引用
4 invokespecial #4 <com/java/Demo1/Test.<init>>//特殊调用Test的默认构造方法,当这条语句执行完,m的初始值就变为了8
7 astore_1//将t的指针指向新创建的对象
8 return
2.DCL(Double Check Lock)单例到底需不需要volatile?
单例:某个类在内存中new出来的对象只有一个
案例1(饿汉式):
public class Test {
private static final Test INSTANCE = new Test();
private Test(){}
private static Test getInstance(){ return INSTANCE; }
public static void main(String[] args) {
Test t1 = Test.getInstance();
Test t2 = Test.getInstance();
System.out.println(t1 == t2);
}
}
案例2(懒汉式):
public class Test {
private static Test INSTANCE;
private Test(){}
private static Test getInstance(){
if(INSTANCE == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Test();
}
return INSTANCE;
}
}
方法上上锁:
private static synchronized Test getInstance(){
if(INSTANCE == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Test();
}
return INSTANCE;
}
方法内判断后上锁:
private static Test getInstance(){
if(INSTANCE == null){
sychronized(Test.class){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Test();
}
}
return INSTANCE;
}
DCL:
private static Test getInstance(){
if(INSTANCE == null){
sychronized(Test.class){
if(INSTANCE == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Test();
}
}
}
return INSTANCE;
}
volatile作用 :
1.保持线程可见性
2.禁止指令重排
指令重排:
CPU速度比内存速度要快100倍,假设有两条指令,第一条指令去内存中取一个数据,第二条指令是在CPU上做个运算,两个操作没有关联性,此时由于取数据操作相对要慢很多,CPU会先执行第二条指令,这是CPU在最底层做的优化机制。
这就是指令重排,目的是为了提高效率。
//int m = 8;
0 new #3 <com/java/Demo1/Test>//int m = 0;
4 invokespecial #4 <com/java/Demo1/Test.<init>>//int m = 8;
7 astore_1//t = new Test();
发生指令重排:
//int m = 8;
0 new #3 <com/java/Demo1/Test>//int m = 0;
7 astore_1//t = new Test();
4 invokespecial #4 <com/java/Demo1/Test.<init>>//int m = 8;
3.对象在内存中的存储布局
普通对象:
数组:
markword:8个字节,包括锁信息等
class pointer:4个字节,指向类的class对象
instance data:长度看具体情况,保存了成员变量
padding:8字节对齐
数组还多一个length长度,4个字节大小,由此也可以知道数组所能创建的最大长度。
4.对象头中保存的信息
markword:
包含了锁信息、identity hashcode(你调用了hashcode方法就会有,不调用就没有)和GC的信息
锁信息:
通过JOL类库我们能够查看一个对象在内存中的布局
没有加锁的对象内存布局:
加锁的对象内存布局:
class pointer:
保存了指向类的class对象的指针,默认开启压缩是4个字节大小,不压缩是8个字节大小。
5.对象怎么定位
1.直接指针
引用直接指向new出来的对象
优点:直接访问
缺点:GC需要移动对象的时候稍麻烦
2.句柄方式
引用会直接指向一个句柄池,句柄池包含实例数据指针和类型数据指针,实例数据指针指向new出来的对象,类型数据指针会指向类的class对象
优点:稳定,垃圾回收时不用频繁改动引用指向
缺点:两次访问
6.对象怎么分配
当new一个对象的时候,首先会尝试在栈中分配,当满足一些条件之后,就会将对象放入栈中(优点:方法一结束,整个栈帧往外一弹里面分配的东西就没了,不需要GC的介入效率非常高);如果没有放入栈中,它会看你的对象大小,如果足够大就会直接放入老年代,然后通过FGC回收;如果大小不够大就会放入TLAB(Thread Local Alication Buffer,线程本地分配缓冲区)中,它会往当前线程在E区内所占有的私有空间中放入对象;如果放满了,就放在E区其他内存空间中同步地放入对象,等待GC清除;如果GC清除不掉,就会进入到survival区进行循环;然后当达到一定年龄的时候就会进入到老年代,没有达到就进入一个新的survival区,继续循环直到被回收。
7.Object o = new Object()在内存中占用多少字节
8个字节markword,4个字节classpointer,4个字节用来对齐,加上默认开启压缩后引用o占4个字节,所以总共20个字节。