以前说了类加载的过程,可是有的读者表示仍是有些知识点没弄清楚,相关面试题也不能思考出结果,因此今天就来总结下类加载、对象实例化
方面的知识点/面试题,帮助你们加深印象。java
全是干货,一网打尽类的基础知识
!先看看下面的问题都能回答上来吗?面试
<clinit>()
方法和<init>()
方法区别。先上图,再描述:多线程
Java
中对象的建立过程包括 类初始化和类实例化两个阶段。
而new
就是建立对象的一种方式,一种时机。框架
当执行到new
的字节码指令的时候,会先判断这个类是否已经初始化,若是没有初始化就要进行类的初始化,也就是执行类构造器<clinit>()
方法。
若是已经初始化了,就直接进行类对象的实例化。函数
类的初始化
,是类的生命周期中的一个阶段,会为类中各个类成员赋初始值。类的实例化
,是指建立一个类的实例的过程。可是在类的初始化以前,JVM
会保证类的装载,连接(验证、准备、解析)
四个阶段都已经完成,也就是上面的第一张图。学习
装载
是指 Java
虚拟机查找.class
文件并生成字节流,而后根据字节流建立java.lang.Class
对象的过程。连接
是指验证建立的类,并将其解析到JVM
中使之可以被 JVM
执行。那到底类加载的时机是何时呢?JVM
并无规范什么时候具体执行,不一样虚拟机的实现会有不一样,常见有如下两种状况:.net
隐式装载
:在程序运行过程当中,当碰到经过 new
等方式生成对象时,系统会隐式调用 ClassLoader
去装载对应的 class 到内存中;显示装载
:在编写源代码时,主动调用 Class.forName()
等方法也会进行 class 装载操做,这种方式一般称为显示装载。因此到这里,大的流程框架就搞清楚了:线程
当JVM
碰到new
字节码的时候,会先判断类是否已经初始化
,若是没有初始化(有可能类尚未加载,若是是隐式装载,此时应该尚未类加载,就会先进行装载、验证、准备、解析
四个阶段),而后进行类初始化
。code
若是已经初始化过了,就直接开始类对象的实例化
工做,这时候会调用类对象的<init>
方法。htm
而后说说具体的逻辑,结合一段类代码:
public class Run { public static void main(String[] args) { new Student(); } } public class Person{ public static int value1 = 100; public static final int value2 = 200; public int value4 = 400; static{ value1 = 101; System.out.println("1"); } { value1 = 102; System.out.println("3"); } public Person(){ value1 = 103; System.out.println("4"); } } public class Student extends Person{ public static int value3 = 300; public int value5 = 500; static{ value3 = 301; System.out.println("2"); } { value3 = 302; System.out.println("5"); } public Student(){ value3 = 303; System.out.println("6"); } }
首先是类装载,连接(验证、准备、解析)。
当执行类准备过程当中,会对类中的静态变量
分配内存,并设置为初始值也就是“0值”
。好比上述代码中的value1,value3
,会为他们分配内存,并将其设置为0。可是注意,用final修饰静态常量value2
,会在这一步就设置好初始值102。
初始化阶段,会执行类构造器<clinit>
方法,其主要工做就是初始化类中静态的(变量,代码块)。可是在当前类的<clinit>
方法执行以前,会保证其父类的<clinit>
方法已经执行完毕,因此一开始会执行最上面的父类Object的<clinit>
方法,这个例子中会先初始化父类Person,再初始化子类Student。
初始化中,静态变量和静态代码块顺序是由语句在源文件中出现的顺序所决定的,也就是谁写在前面就先执行谁。因此这里先执行父类中的value1=100,value1 = 101
,而后执行子类中的value3 = 300,value3 = 301
。
接着就是建立对象的过程,也就是类的实例化,当对象被类建立时,虚拟机会分配内存
来存放对象本身的实例变量和父类继承过来的实例变量,同时会为这些事例变量赋予默认值(0值)。
分配完内存后,会初始化父类的普通成员变量(value4 = 400)
,和执行父类的普通代码块(value1=102)
,顺序由代码顺序决定。
执行父类的构造函数(value1 = 103)
。
父类实例化完了,就实例化子类,初始化子类的普通成员变量(value5 = 500)
,执行子类的普通代码块(value3 = 302)
,顺序由代码顺序决定。
执行子类的构造函数(value3 = 303)
。
因此上述例子打印的结果是:
123456
总结一下执行流程
就是:
父类静态变量和静态代码块;
子类静态变量和静态代码块;
父类普通成员变量和普通代码块;
父类的构造函数;
子类普通成员变量和普通代码块;
子类的构造函数。
最后,你们再结合流程图
好好梳理一下:
在同一个类加载器下,一个类型只会被初始化一次,刚才说到new对象
是类初始化的一个判断时机,其实一共有六种
可以触发类初始化的时机:
虚拟机启动时,初始化包含 main
方法的主类;
遇到 new
等指令建立对象实例时,若是目标对象类没有被初始化则进行初始化操做;
当遇到访问静态方法或者静态字段的指令时,若是目标对象类没有被初始化则进行初始化操做;
子类的初始化过程若是发现其父类尚未进行过初始化,则须要先触发其父类的初始化;
使用反射 API
进行反射调用时,若是类没有进行过初始化则须要先触发其初始化;
第一次调用 java.lang.invoke.MethodHandle
实例时,须要初始化 MethodHandle
指向方法所在的类。
不会,<clinit>()
方法是阻塞的,在多线程环境下,若是多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()
,其余线程都会被阻塞。
new
关键字建立对象newInstance
方法,Constructor
类的newInstance
方法(反射机制)Clone
方法建立对象<clinit>()
方法和<init>()
方法区别。<clinit>()
方法发生在类初始化阶段,会执行类中的静态类变量的初始化和静态代码块中的逻辑,执行顺序就是语句在源文件中出现的顺序。
<init>()
方法发生在类实例化阶段,是默认的构造函数,会执行普通成员变量的初始化和普通代码块的逻辑,执行顺序就是语句在源文件中出现的顺序。
刚才都说了先初始化,再实例化,若是这个问题能够的话那不是打脸了吗?
没错,要打脸了哈哈。
确实是先进行类的初始化,再进行类的实例化,可是若是咱们在类的初始化阶段
就直接实例化对象呢?好比:
public class Run { public static void main(String[] args) { new Person2(); } } public class Person2 { public static int value1 = 100; public static final int value2 = 200; public static Person2 p = new Person2(); public int value4 = 400; static{ value1 = 101; System.out.println("1"); } { value1 = 102; System.out.println("2"); } public Person2(){ value1 = 103; System.out.println("3"); } }
嘿嘿,这时候该怎么打印结果呢?
按照上面说过的逻辑,应该是先静态变量和静态代码块,而后普通成员变量和普通代码块,最后是构造函数。
可是由于静态变量又执行了一次new Person2()
,因此实例化过程被强行提早
了,在初始化过程当中就进行了实例化。这段代码的结果就变成了:
23123
因此,实例化不必定要在类初始化结束以后才开始初始化,有可能在初始化过程当中
就进行了实例化。
学了上面的内容,这个问题就很简单了:
类的初始化
,是指在类装载,连接以后的一个阶段,会执行<clinit>()
方法,初始化静态变量,执行静态代码块等。只会执行一次。
类的实例化
,是指在类彻底加载到内存中后建立对象的过程,会执行<init>()
方法,初始化普通变量,调用普通代码块。能够被调用屡次。
那咱们就试试举例出最多的状况,其实也就是每一个要通过的地方都对实例变量进行一次赋值:
对象被建立时候
,分配内存会把实例变量赋予默认值,这是确定会发生的。实例变量自己初始化的时候
,就给他赋值一次,也就是int value1=100。初始化代码块的时候
,也赋值一次。构造函数中
,在进行赋值一次。一共四次,看代码:
public class Person3 { public int value1 = 100; { value1 = 102; System.out.println("2"); } public Person3(){ value1 = 103; System.out.println("3"); } }
http://www.javashuo.com/article/p-rtbvcfpv-ey.html
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1860
https://www.jianshu.com/p/8a14ed0ed1e9
有一块儿学习的小伙伴能够关注下❤️ 个人公众号——码上积木,天天剖析一个知识点,咱们一块儿积累知识。公众号回复111可得到面试题《思考与解答》以往期刊。