【JVM】JVM类加载全过程以及双亲委派模型详解

在了解类加载的双亲委派模型之前,先了解一下以下前置知识:

1. Java运行时一个类是什么时候被加载的?

以HotSpot 虚拟机是按需加载,在需要用到该类的时候加载这个类,那什么时候用到该类呢?用到该类,无外乎出现在创建类的对象、访问类的静态变量和方法、反射获取类对象等情况。

2. JVM一个类的加载过程?

一个类从加载到jvm内存,到从jvm内存卸载,它的整个生命周期会经历7个阶段:

  1. 加载(Loading)
    从classpath、jar包、网络、某个磁盘位置下的类的class二进制字节流读进来,在内存中生成一个代表这个类的java.lang.Class对象放入元空间,此阶段我们程序员可以干预,我们可以自定义类加载器来实现类的加载;
  2. 验证(Verification)
    验证Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证虚拟机的安全;
  3. 准备(Preparation)
    类变量赋默认初始值,int为0, long为0L, boolean为false,引用类型为null;常量赋正式值;
  4. 解析(Resolution)
    把符号引用翻译为直接引用
  5. 初始化(Initialization)
    当我们new一个类的对象、访问一个类的静态属性、修改一个类的静态属性、调用一个类的静态方法、用反射API对一个类进行调用、初始化当前类,其父类也会被初始化…那么这些都会触发类的初始化;
  6. 使用(Using)
    使用这个类
  7. 卸载(Unloading)
    出现以下情况时,会卸载该类:
    (1)该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例;
    (2)加载该类的ClassLoader 已经被GC;
    (3)该类的java.lang.Class对象没有在任何地方被引用,如不能在任何地方通过反射访问该类时。

其中验证、准备、解析三个阶段统称为连接(Linking) :
在这里插入图片描述

3. 一个类被初始化的过程?

类的初始化阶段,Java虚拟机才真正开始执行类中编写的Java程序代码;
进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,才真正初始化类变量和其他资源;
下面一代码演示:

public class Test01 {
    //静态常量==准备阶段
    public static final String staticConstantField = "静态常量";

    //静态变量==准备阶段赋值为null,初始化阶段赋值为静态变量
    public static String staticField = "静态变量";

    //变量==创建对象的时候赋值
    public String field = "变量";

    //静态初始化块==初始化阶段执行static
    static {
        System.out.println(staticConstantField);
        System.out.println(staticField);
        System.out.println("静态初始化块");
    }

    //初始化块==创建对象的时候执行
    {
        System.out.println(field);
        System.out.println("初始化块");
    }

    //构造器==创建对象的时候执行
    public Test01() {
        System.out.println("构造器");
    }

    public static void main(String[] args) {
        new Test01();
    }
}

执行结果:
在这里插入图片描述

4. 类加载器架构?

自JDK1.2开始,Java一直保持着三层类加载器架构:
在这里插入图片描述
三个类加载器各自的作用:
1. 启动类加载器(Bootstrap ClassLoader)
启动类加载器是根的类加载器),依靠c++实现,启动类加载器加载jdk中<JAVA_HOME>\jre\lib\目录下的部分jar包,如rt.jar、resources.jar、charsets.jar等,(并非该目录下所有jar包)还有就是加载被-Xbootclasspath参数所指定的路径中存放的类库;

2.扩展类加载器(Extension ClassLoader)
sun.misc.Launcher$ExtClassLoader,扩展类加载器主要加载jdk中<JAVA_HOME>\jre\lib\ext目录下的jar包以及被 java.ext.dirs系统变量所指定的路径中所有的类库;

3.应用程序类加载器(Application ClassLoader)
系统的类加载器sun.misc.Launcher$AppClassLoader,主要加载用户类路径(ClassPath)上所有的类库;

注意:此处的三层架构并不代表三个类加载器之间是继承关系,他们之间的关系如下,都是继承ClassLoader这一抽象类
在这里插入图片描述
在这里插入图片描述

5. JVM类加载的双亲委派模型(重点)

在这里插入图片描述
双亲委派模型的工作过程是:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当上一层类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到这个类)时,下一层类加载器才会尝试自己去加载;

以加载String类为例:
首先会尝试让AppClassLoader去加载,但是AppClassLoader自己不去加载,它会委派ExtClassLoader去加载,但是ExtClassLoader自己也不会加载,它会继续向上委派BootStrap ClassLoader去加载,于是,BootStrap ClassLoader就会在<JAVA_HOME>\jre\lib\目录下相应的jar包去查找,最后在rt.jar包中找到了String类,于是类加载结束。

如果一个类在BootStrap ClassLoader中未加载到,会继续向下(ExtClassLoader)尝试加载,若还是未加载到,则会继续向下到AppClassLoader中加载,若最后都未加载都,则会抛出ClassLoaderFoundException异常。

6. JDK为什么要设计双亲委派模型,有什么好处?
  • 1、确保安全,避免Java核心类库被修改;
    如jdk中,有java.lang.String这个类,我们再自己定义一个相同结构的类时,就会出错。
  • 2、避免重复加载;
  • 3、保证类的唯一性;
7. 可以打破JVM双亲委派模型吗?如何打破JVM双亲委派模型?

双亲委派模型是可以打破的;
来观察一下双亲委派模型源码:
在这里插入图片描述
双亲委派模型的核心源码:

synchronized (getClassLoadingLock(name)) {
    // First, check if the class has already been loaded
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        long t0 = System.nanoTime();
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // ClassNotFoundException thrown if class not found
            // from the non-null parent class loader
        }

        if (c == null) {
            // If still not found, then invoke findClass in order
            // to find the class.
            long t1 = System.nanoTime();
            c = findClass(name);

            // this is the defining class loader; record the stats
            sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
            sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
            sun.misc.PerfCounter.getFindClasses().increment();
        }

综上可得,如果想要打破JVM双亲委派模型,那么就自定义一个类加载器,继承classLoader类,重写其中的loadClass方法,使其不进行双亲委派即可。

后续博客会继续更新JVM内存模型噢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值