-
装载是指
Java
虚拟机查找.class
文件并生成字节流,然后根据字节流创建java.lang.Class
对象的过程。 -
链接是指验证创建的类,并将其解析到
JVM
中使之能够被JVM
执行。
那到底类加载的时机是什么时候呢?JVM
并没有规范何时具体执行,不同虚拟机的实现会有不同,常见有以下两种情况:
-
隐式装载:在程序运行过程中,当碰到通过
new
等方式生成对象时,系统会隐式调用ClassLoader
去装载对应的 class 到内存中; -
显示装载:在编写源代码时,主动调用
Class.forName()
等方法也会进行 class 装载操作,这种方式通常称为显示装载。
所以到这里,大的流程框架就搞清楚了:
-
当
JVM
碰到new
字节码的时候,会先判断类是否已经初始化
,如果没有初始化(有可能类还没有加载,如果是隐式装载,此时应该还没有类加载,就会先进行装载、验证、准备、解析
四个阶段),然后进行类初始化
。 -
如果已经初始化过了,就直接开始类对象的
实例化
工作,这时候会调用类对象的<init>
方法。
结合例子说明
然后说说具体的逻辑,结合一段类代码:
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
关键字创建对象 -
使用Class类的
newInstance
方法,Constructor
类的newInstance
方法(反射机制) -
使用
Clone
方法创建对象 -
使用(反)序列化机制创建对象
<clinit>()
方法和<init>()
方法区别。
-
<clinit>()
方法发生在类初始化阶段,会执行类中的静态类变量的初始化和静态代码块中的逻辑,执行顺序就是语句在源文件中出现的顺序。 -
<init>()
方法发生在类实例化阶段,是默认的构造函数,会执行普通成员变量的初始化和普通代码块的逻辑,执行顺序就是语句在源文件中出现的顺序。
在类都没有初始化完毕之前,能直接进行实例化相应的对象吗?
刚才都说了先初始化,再实例化,如果这个问题可以的话那不是打脸了吗?
没错,要打脸了哈哈。
确实是先进行类的初始化,再进行类的实例化,但是如果我们在类的初始化阶段
就直接实例化对象呢?比如:
public class Run {
public static void main(String[] args) {
new Person2();
}
}
public class Person2 {
结尾
好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划,可以来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。
这里放上一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套进阶学习的视频及面试专题资料包,在这里免费分享给大家,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家~
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-VgJoFsNW-1710964692704)]
[外链图片转存中…(img-57xlHlhU-1710964692705)]
[外链图片转存中…(img-Dw0C3PTy-1710964692705)]
[外链图片转存中…(img-EdVnECZd-1710964692705)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-LudqAqaO-1710964692706)]