类加载,其实是设计一个运行时环境的一个重要的核心的功能,此处学习的是常见的面试题❗
类加载是要干啥?就是把.class文件,加载到内存中,构建成类对象。
对于一个类来说,它的生命周期是这样的:
其中前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步我们都属于连接,所以对于类加载来说总共分为以下几个步骤:
也就是说,整个类加载过程,分成三个大的步骤!!!Loading(加载)、Linking(连接)、and Initialization(初始化)
小tip:建议面试回答的时候尽量回答英文,要不然你第一步就说加载,面试官万一问你说类加载,你第一步就加载,是全部都加载完了么,容易产生歧义,用英文回答可能更严谨一点❗😊
总结下来就是先找到对应的 .class文件,然后打开并读取 .class 文件,同时初步生成一个类对象
Loading中的一个关键环节就是, .class文件到底里面是啥样的❓🤔
会根据上面图中的格式,把读取并解析到的信息,初步的填写到类对象中
只要这个类被用到了,就要先加载这个类(实例化,调用方法,调用静态方法,被继承…都算被用到)
class A{
public A(){
System.out.println("A的构造方法");
}
{
System.out.println("A的构造代码块");
}
static {
System.out.println("A的静态代码块");
}
}
class B extends A{
public B(){
System.out.println("B的构造方法");
}
{
System.out.println("B的构造代码块");
}
static {
System.out.println("B的静态代码块");
}
}
public class Test extends B{
public static void main(String[] args) {
new Test();
new Test();
}
}
咱们的程序是从main方法开始执行,main这里是Test的方法,因此要执行main,就需要先加载Test,而Test继承自B,要加载Test,就要先加载B,B继承自A,要加载B,就要先加载A。
加载过程如下:
结果如下:
要记住这几个大的原则:
Java类初始化顺序: 父类静态变量–>父类静态代码块–>子类静态变量–>子类静态代码块–>父类非静态变量–>父类非静态代码块–>父类构造函数—>子类非静态变量–>子类非静态代码块—>子类构造函数
JVM里提供了专门的对象,叫做类加载器,负责进行类加载,当然找文件的过程也是类加载器来负责的。由于 .class 文件可能放置的位置有很多,有的要放到JDK目录里,有的放到项目目录里,还有的在其他特定位置,因此JVM里面提供了多个类加载器,每个类加载器负责一个片区。
默认的类加载器主要是以下几个:
程序猿还可以自定义类加载器,来加载其他目录中的类,比如:Tomcat就自定义了类加载器,用来专门加载webapps里面的 .class
这个东西是类加载中的一个环节,处于Loading阶段。双亲委派模型描述的就是JVM中的类加载器,如何根据类的全限定名(java.lang.String)找到 .class 文件的过程。描述了这个找目录过程,也就是上述类加载器是如何配合的。
上述这一套查找规则,就称为“双亲委派模型”,看到这,有的童鞋不免有疑惑了,不是说双亲委派模型嘛,这怎么光父亲,母亲嘞❓🤔其实是parent既可以表示父亲,也能表示母亲,有些资料上直接翻译成"双亲",指的是双亲中的某一 个。
为啥JVM要这么设计?
理由就是:一旦程序猿自己写的类,和标准库中的类,全限定类名重复了,也能够顺利的加载到标准库中的类❗❗❗
就比如说:java.lang.String这样的类,如果程序尝试去加载这个类的话,加载的是标准库中的类,如果想加载到我们自定义的类,那是要通过ApplicationClassLoader来加载的,然后ApplicationClassLoader会先上报给ExternsionClassloader,然后它再上报给BootstrapClassLoader,然后BootstrapClassLoader就直接加载了,所以也就轮不到ApplicationClassLoader去加载自己的类了。
如果是自定义类加载器,是否也要遵守双亲委派模型❓
可以遵守,也可以不遵守,看需求。
比如:像tomcat加载webapp中的类,就没有遵守(因为遵守了也没啥意义)