关于java:面试官在-Java-中-new-一个对象的流程是怎样的彻底被问懵了

31次阅读

共计 5776 个字符,预计需要花费 15 分钟才能阅读完成。

对象怎么创立,这个太相熟了,new 一下(其实还有很多路径,比方反射、反序列化、clone 等,这里拿最简略的 new 来讲):

Dog dog = new Dog();

咱们总是习惯于固定语句的执行,却对于背地的实现过程不足认知,而了解这个过程对前面艰涩难懂的反射和代理其实会有很大帮忙,所以请务必学好这块内容。

在看这篇文章之前,啰嗦一句:如果你死记硬背上面所说的流程等于白看,就算当初记住了,一个礼拜后呢,一个月后你又能记得多少,因为对象创立过程这个知识点平时的工作中根本不会波及到,太底层了,背熟的知识点不常常加以使用容易忘记,所以我的倡议是什么呢,流程做到心里大略有个数,其中波及到要害的知识点记牢就能够了。

JVM 内存

先简略说下 java 虚拟机内存模型和寄存内容的区别,两局部:

  • 栈内存 寄存根本类型数据和对象的援用变量,数据能够间接拿来拜访,速度比堆快
  • 堆内存 寄存创立的对象和数组,会由 java 虚拟机的主动垃圾回收来治理(GC), 创立一个对象放入堆内的同时也会在栈中创立一个指向该对象堆内存中的地址援用变量,上面说的对象就是存在该内存中

上面咱们就依照对象生成的过程来一一解说参加其中过程的各个概念

首先有这么一个类,前面的初始化基于这个解说:

/**
 * @author 炜哥
 * @since 2021-04-18 11:01:41
 *
 * 执行程序:(优先级从高到低。)动态代码块 > 结构代码块 > 构造方法 > 一般办法。* 其中动态代码块只执行一次。结构代码块在每次创建对象是都会执行。*/
public class Dog {

    // 默认狗狗的最大年龄是 16 岁
    private static int dog_max_age = 16;

    // 狗狗的名字
    private String dog_name;

    {System.out.println("狗狗的结构代码块");
    }

    static {System.out.println("狗狗的动态代码块");
    }

    // 无参结构器成心没设
    // 有参结构器
    public Dog(String dog_name) {this.dog_name = dog_name;}

    public void getDogInfo(){System.out.println("名字是:"+dog_name + "年龄:" + dog_max_age);
    }

    // 狗叫
    public static void barking(){System.out.println("汪汪汪~~~");
    }
}

JVM 生成.class 文件

一个 java 文件会在编译期间被初始化生成.class 字节码文件,字节码文件是专门给 JVM 浏览的,咱们平时吭哧吭哧写的一行行代码最终都会被编译成机器能看懂的语句,这个文件前面会被类加载器加载到内存。

类加载器加载.class 文件

《深刻了解 Java 的虚拟机》中大略有这么一句话:在虚拟机遇到一条 new 的指令时,会去查看一遍在动态常量池中是否定位到一个类的符号援用(就这个类的门路 + 名字),并且查看这个符号援用代表的类是否已被加载、解析和初始化过。如果不是第一次应用,那必须先执行相应的类加载过程,这个过程由类加载器来实现。

类加载字面意思就能够了解成加载 class 文件,更精确点的说法就是会把 class 文件变成一个二进制流加载到内存中,即把类的形容信息加载到 Metaspace,至于类加载器如何找到并把一个 class 文件转成 IO 流加载到内存中,我前面会专门写一篇对于类加载器的文章,这里就只有了解创建对象中有这么一步就行了。不过这外面有很重要的概念不得不讲:Class 对象

常识扩大:Class 对象

划重点,这是个十分重要的概念,了解它对于了解前面的反射和代理会有很大的帮忙

类加载器 ClassLoader 加载 class 文件时,会把类里的一些数值常量、办法、类信息等加载到内存中,称之为类的元数据,最终目标是为了生成一个 Class 对象用来形容类,这个对象会被保留在.class 文件里,可能有老手看到这里会比拟懵逼,class 也有对象?

当然了,Class 是个实实在在的类(用来形容类的类,比拟拗口),有构造方法(private,意味着能够生成对象,但不能手动生成,由 JVM 主动创立 Class 对象), 类加载器会给每个 java 文件都创立一个 Class 对象,用来形容类,我画个图:

// 以下操作只能由 jvm 实现,咱们手动做不了
Class cls1 = new Class(Dog.class.getClassLoader());
Class cls2 = new Class(Cat.class.getClassLoader());
Class cls3 = new Class(People.class.getClassLoader());

这个 Class 对象除了形容对应的类之外还有什么作用呢?也能够生成对象,就是 java 的反射概念(通过 Class 实例获取类信息)下面说了,Class 类是用来形容像 People.Class 类的类,那么它外面必定蕴含了所有可能形容该 class 的所有属性,比方类名、办法、接口等,咱们先到 Class 类源码中瞄一眼:

这外面有个办法 newInstance(),即创建对象,我把源代码贴出来并简略解析下:

@CallerSensitive
public T newInstance()
    throws InstantiationException, IllegalAccessException
    {if (System.getSecurityManager() != null) {checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
        }

        if (cachedConstructor == null) {if (this == Class.class) {
                throw new IllegalAccessException("Can not call newInstance() on the Class for java.lang.Class"
                );
            }
            try {Class<?>[] empty = {};
                // 申明无参结构对象
                final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
                // Disable accessibility checks on the constructor
                // since we have to do the security check here anyway
                // (the stack depth is wrong for the Constructor's
                // security check to work)
                java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() {public Void run() {c.setAccessible(true);
                                return null;
                            }
                        });
                cachedConstructor = c;
            } catch (NoSuchMethodException e) {
             // 如果 class 中没有无参构造方法,那么抛 InstantiationException 谬误
                throw (InstantiationException)
                    new InstantiationException(getName()).initCause(e);
            }
        }
        Constructor<T> tmpConstructor = cachedConstructor;
        // Security check (same as in java.lang.reflect.Constructor)
        int modifiers = tmpConstructor.getModifiers();
        if (!Reflection.quickCheckMemberAccess(this, modifiers)) {Class<?> caller = Reflection.getCallerClass();
            if (newInstanceCallerCache != caller) {Reflection.ensureMemberAccess(caller, this, null, modifiers);
                newInstanceCallerCache = caller;
            }
        }
        // Run constructor
        try {
         // 最终还是调用了无参结构器对象的 newInstance 办法
            return tmpConstructor.newInstance((Object[])null);
        } catch (InvocationTargetException e) {Unsafe.getUnsafe().throwException(e.getTargetException());
            // Not reached
            return null;
        }
    }

首先搞清楚 newInstance 两种办法区别:

Class.newInstance() 只可能调用无参的构造函数,即默认的构造函数,咱们在 Class 源码里也看到了其实最终还是调用了无参结构器对象 ConstructornewInstance 办法,举个栗子:Dog.class 中是没有无参构造方法,那么会间接抛出 InstantiationException 异样:

//Dog 类中只有一个 dog_name 的有参构造方法
Class c = Class.forName("com.service.ClassAnalysis.Dog");
Dog dog = (Dog) c.newInstance();// 间接抛 InstantiationException 异样

Constructor.newInstance() 能够依据传入的参数,调用任意结构构造函数,也能够反射公有结构器(理解就行)

//Dog 类中只有一个 dog_name 的有参构造方法
Constructor cs = Dog.class.getConstructor(String.class);
Dog dog = (Dog) cs.newInstance("小黑");// 执行没有问题

然而这外面的 newInstance跟咱们这次要说的 new 办法存在区别,两者创建对象的形式不同,创立条件也不同:

  • 应用 newInstance 时必须要保障这类曾经加载并且曾经建设连贯,就是曾经被类记录器加载结束,而 new 不须要
  • class 对象的 newInstance 办法只能用无参结构,下面曾经提到了,而 new 不须要
  • 前者应用的是类加载机制,是一种办法,后者是创立一个新类,一种关键字

这个不能说newInstance 不不便,相同它在反射、工厂设计模式、代理中施展了重要作用,后续我也会写下代理和反射,因为了解起来的确有点绕。还有一点须要留神,不论以哪种形式创建对象,对应的 Class 对象都是同一个

Dog dog1 = new Dog("旺财");
Dog dog2 = new Dog("小黑");
Class c = Class.forName("com.service.classload.Dog");// 为了测试,加了无参结构
Dog dog3 = (Dog) c.newInstance();
System.out.println(dog1.getClass() == dog2.getClass());
System.out.println(dog1.getClass() == dog3.getClass());

连贯和初始化

在此阶段首先为动态 static 变量内存中调配存储空间,设立初始值(还未被初始化)比方:

public static int i = 666;// 被类加载器加载到内存时会执行,赋予一个初始值
public static Integer ii = new Integer(666);// 也被赋值一个初始值

但请留神,实际上 i 的初始值是 0,不是 666,其余根本数据类型比方 boolean 的初始值就是 false,以此类推。如果是援用类型的成员变量 ii 那么初始值就是 null。

Dog dog = new Dog("旺财");// 在这里打个断点

执行,首先会执行动态成员变量初始化,默认值是 0:

但有例外,如果加上了 final 修饰词那么初始值就是设定的值。

接着对曾经调配存储空间的动态变量真正赋值,比方为下面的 dog_max_age 赋值 16,还有执行动态代码块,也就是相似上面的代码:

static {System.out.println("狗狗的动态代码块");
}

到这为止,类的加载过程才算实现。

创立实例

在加载类结束后,对象的所需大小依据类信息就能够确认了,具体创立的步骤如下:

  • 先给对象分配内存(包含本类和父类的所有实例变量,不包含下面的动态变量),并设置默认值,如果有援用对象那么会在栈内存中申请一个空间用来指向的理论对象。
  • 执行初始化代码实例化,先初始化父类再初始化子类,赋予给定值(尊重前辈是 java 的传统美德)
  • 对象实例化结束后如果存在援用对象的话还须要 把第一步的栈对象指向到堆内存中的理论对象,这样一个真正可用的对象才被创立进去。

说了这么多预计很多人都没概念,懵逼状态中,其实很简略,咱们只有记住 new 的创建对象就两步:初始化和实例化,再给你们搞一张图: 能够简略了解②③④为初始化⑤实例化(可恶,我这该死的责任感!)

本文不指望你能使劲弄懂 java 虚拟机底层加载创建对象的过程(其实有些步骤我都懒得讲了,因为说进去也都十分理论化,没多大意思),是想让你晓得对象的诞生过程有哪几个重要概念参加了,弄懂这些概念比起单单晓得对象创立的过程有意义的多:

  • 类加载器,能够找找网上的材料,蛮多的,这块内容做个理解就行
  • Class 类和 Class 对象的概念,请重点把握,不然了解反射和动静代理很吃力,spring 的源码也会难以了解
  • 栈内存和堆内存以及对应的根本类型和援用类型,也很重要,争取能根本了解

起源:blog.csdn.net/qq_16887951/article/details/115872678

近期热文举荐:

1.1,000+ 道 Java 面试题及答案整顿(2022 最新版)

2. 劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4. 别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java 开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞 + 转发哦!

正文完
 0