建议看看!这几个java对象高频面试题,面试BAT肯定会碰到

146 阅读4分钟

请解释一下对象创建的过程

案例:

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

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;

对象在内存中的存储布局

普通对象:

数组:

markword:8个字节,包括锁信息等 class pointer:4个字节,指向类的class对象 instance data:长度看具体情况,保存了成员变量 padding:8字节对齐 数组还多一个length长度,4个字节大小,由此也可以知道数组所能创建的最大长度。

对象头中保存的信息

markword:

包含了锁信息、identity hashcode(你调用了hashcode方法就会有,不调用就没有)和GC的信息

锁信息:

通过JOL类库我们能够查看一个对象在内存中的布局

没有加锁的对象内存布局:

加锁的对象内存布局:

class pointer: 保存了指向类的class对象的指针,默认开启压缩是4个字节大小,不压缩是8个字节大小。

对象怎么定位

1.直接指针

引用直接指向new出来的对象 优点:直接访问 缺点:GC需要移动对象的时候稍麻烦

2.句柄方式

引用会直接指向一个句柄池,句柄池包含实例数据指针和类型数据指针,实例数据指针指向new出来的对象,类型数据指针会指向类的class对象 优点:稳定,垃圾回收时不用频繁改动引用指向 缺点:两次访问

对象怎么分配

当new一个对象的时候,首先会尝试在栈中分配,当满足一些条件之后,就会将对象放入栈中(优点:方法一结束,整个栈帧往外一弹里面分配的东西就没了,不需要GC的介入效率非常高);如果没有放入栈中,它会看你的对象大小,如果足够大就会直接放入老年代,然后通过FGC回收;如果大小不够大就会放入TLAB(Thread Local Alication Buffer,线程本地分配缓冲区)中,它会往当前线程在E区内所占有的私有空间中放入对象;如果放满了,就放在E区其他内存空间中同步地放入对象,等待GC清除;如果GC清除不掉,就会进入到survival区进行循环;然后当达到一定年龄的时候就会进入到老年代,没有达到就进入一个新的survival区,继续循环直到被回收。

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

8个字节markword,4个字节classpointer,4个字节用来对齐,加上默认开启压缩后引用o占4个字节,所以总共20个字节。

最后

感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!