【JVM】类加载相关面试题——类加载过程、双亲委派模型


引言:

类加载,其实是设计一个运行时环境的一个重要的核心的功能,此处学习的是常见的面试题❗
类加载是要干啥?就是把.class文件,加载到内存中,构建成类对象。

类加载过程

对于一个类来说,它的生命周期是这样的:
在这里插入图片描述
其中前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步我们都属于连接,所以对于类加载来说总共分为以下几个步骤:

  1. 加载
  2. 连接
    • 验证
    • 准备
    • 解析
  3. 初始化

也就是说,整个类加载过程,分成三个大的步骤!!!Loading(加载)、Linking(连接)、and Initialization(初始化)

小tip:建议面试回答的时候尽量回答英文,要不然你第一步就说加载,面试官万一问你说类加载,你第一步就加载,是全部都加载完了么,容易产生歧义,用英文回答可能更严谨一点❗😊

  1. Loading环节
    “加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载 Class
    Loading 是不同的,一个是加载 Loading 另一个是类加载 Class Loading,所以不要把二者搞混了❗❗❗
    在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:
    1)通过一个类的全限定名来获取定义此类的二进制字节流。
    2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

总结下来就是先找到对应的 .class文件,然后打开并读取 .class 文件,同时初步生成一个类对象

Loading中的一个关键环节就是, .class文件到底里面是啥样的❓🤔
在这里插入图片描述

会根据上面图中的格式,把读取并解析到的信息,初步的填写到类对象中

  1. Linking 连接一般就是建立好多个实体之间的联系
    1)验证(Verification)
    主要就是验证读到的内容是不是和规范中规定的格式完全匹配,如果发现这里读到的数据格式不符合规范,就会类加载失败,并且抛出异常
    2)准备(Preparation)
    准备阶段是正式 为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值(为0) 的阶段
    3)解析(Resolution)
    解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。
    意思是由于在 .class文件中,常量是集中放置的,每个常量有一个编号,.class 文件的结构体里初始情况只是记录了编号,所以就需要根据编号找到对应的内容,填充到类对象中。
  2. Initialization初始化
    真正对类对象进行初始化,尤其是针对静态成员

经典面试题

什么时机会触发某个类的加载(代码举例)?

只要这个类被用到了,就要先加载这个类(实例化,调用方法,调用静态方法,被继承…都算被用到)

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。

加载过程如下:
在这里插入图片描述
结果如下:
在这里插入图片描述

要记住这几个大的原则:

  1. 类加载阶段会进行静态代码块的执行,要想创建实例,势必要先进行类加载;
  2. 静态代码块只是类加载阶段执行一次
  3. 构造方法和构造代码块,每次实例化都会执行,构造代码块在构造方法前面
  4. 父类执行在前,子类执行在后
  5. 咱们的程序是从main开始执行,main这里是Test的方法,因此要执行main就需要先加载Test

Java类初始化顺序: 父类静态变量–>父类静态代码块–>子类静态变量–>子类静态代码块–>父类非静态变量–>父类非静态代码块–>父类构造函数—>子类非静态变量–>子类非静态代码块—>子类构造函数

类加载器

JVM里提供了专门的对象,叫做类加载器,负责进行类加载,当然找文件的过程也是类加载器来负责的。由于 .class 文件可能放置的位置有很多,有的要放到JDK目录里,有的放到项目目录里,还有的在其他特定位置,因此JVM里面提供了多个类加载器,每个类加载器负责一个片区。

默认的类加载器主要是以下几个:

  1. BootstrapClassLoader
    负责加载标准库中的类(String ,ArrayList ,Random ,Scanner…)
  2. ExternsionClassl oader
    负责加载JDK扩展的类(现在很少会用到)
  3. ApplicationClassLoader
    负责加载当前项目目录中的类

程序猿还可以自定义类加载器,来加载其他目录中的类,比如:Tomcat就自定义了类加载器,用来专门加载webapps里面的 .class

关于“双亲委派模型”

这个东西是类加载中的一个环节,处于Loading阶段。双亲委派模型描述的就是JVM中的类加载器,如何根据类的全限定名(java.lang.String)找到 .class 文件的过程。描述了这个找目录过程,也就是上述类加载器是如何配合的。
在这里插入图片描述

上述这一套查找规则,就称为“双亲委派模型”,看到这,有的童鞋不免有疑惑了,不是说双亲委派模型嘛,这怎么光父亲,母亲嘞❓🤔其实是parent既可以表示父亲,也能表示母亲,有些资料上直接翻译成"双亲",指的是双亲中的某一 个。

为啥JVM要这么设计?
理由就是:一旦程序猿自己写的类,和标准库中的类,全限定类名重复了,也能够顺利的加载到标准库中的类❗❗❗

就比如说:java.lang.String这样的类,如果程序尝试去加载这个类的话,加载的是标准库中的类,如果想加载到我们自定义的类,那是要通过ApplicationClassLoader来加载的,然后ApplicationClassLoader会先上报给ExternsionClassloader,然后它再上报给BootstrapClassLoader,然后BootstrapClassLoader就直接加载了,所以也就轮不到ApplicationClassLoader去加载自己的类了。

如果是自定义类加载器,是否也要遵守双亲委派模型❓

可以遵守,也可以不遵守,看需求。

比如:像tomcat加载webapp中的类,就没有遵守(因为遵守了也没啥意义)

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值