请介绍类加载过程,什么是双亲委派模型?

截止上一篇专栏,已经介绍过很多关于jvm,线程方面的知识了,这些都是容易在社招中问到的,后面还会继续介绍类加载,连接池各种参数,GC调优,分布式事务,SQL锁,尽量在7月份之前全部总结完成,po主也是也是为了能早日蒙混进大厂啊,马上就要大三了,快没时间了,哈哈,希望大家还年轻的,可以好好努力,继续加油,多学点东西继续努力。

这次就来介绍一下和上面提到的同样重要的类加载过程和双亲委派模型!

简述

从一般角度去说,java类加载的过程分为三个步骤:加载、链接、初始化。接下来,对这三方面进行一下概述:

1. 加载

它是java将字节码数据从不同的数据源中读取到jvm里的,并且映射为jvm可以识别的数据结构(class对象),这里的数据源,可以是jar,可以是class文件,甚至网络数据源,但是如果数据不是以上结构,就会抛出异常:ClassFormatError。

加载阶段是用户参与的,可以自定义类加载器,实现自己的类加载阶段。

2. 链接

这是核心的步骤,简单来说就是把原始的类定义信息平滑地转入jvm运行的过程中。这里可分为三步:

  • 验证。这是jvm安全的保障,jvm需要检验字节码的数据结构是否满足要求,不然就抛出异常VerifyError,这样就防止了恶意信息或者不合规的信息危害jvm的运行,验证阶段有可能触发更多class的加载。

  • 准备。创建类或接口的静态变量,并且给予初值。但是这里的初始化和下面的显式初始化是有区别的,区别在于分配所需要的内存的时候不会去更进一步执行jvm命令

  • 解析。上一篇专栏介绍到了常量池,希望大家还记得,这一步会将常量池的符号引用替换为直接引用。

3. 初始化

这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动作以及执行类定义中的静态初始化块中的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑

双亲委派模型

简单来说,当类加载器(Class-Loader)试图加载某个类型的时候,除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。使用委派模型的目的是避免重复加载java类型。

问题延伸

既然上面提到了静态变量,那么常量和静态变量有什么不同呢?
可以看以下的代码

在这里插入图片描述

普通原始类型静态变量和引用类型(即使是常量),是需要额外调用putstatic等jvm指令的,这些是在显式初始化阶段执行,而不是在准备阶段调用;而原始类型常量,则不需要这样的步骤。

关于类加载的过程的更多细节,可以参考如下:

  • 如果要真正理解双亲委派,就要理解java类加载器的架构和职责,至少要懂具体有哪些内建的类加载器;以及如何自定义类加载器。
  • 从应用角度去说,加入java启动很慢,有什么方法减少java类加载的开销?

扩展

首先,从架构角度,可以看看java8以前类加载器的结构,下面来介绍三种oracle jdk内建的类加载器。

1. 启动类加载器(Bootstrap Class-Loader)

加载 jre/lib下面的jar文件,如rt.jar。它是个超级公民,即使是在开启了Security Manager的时候,JDK仍赋予了它加载的程 序AllPermission。

对于做底层开发的工程师,有的时候可能不得不去试图修改JDK的基础代码,也就是通常意义上的核心类库,我们可以使用下面的命令行参数。
(图取自极客时间)

在这里插入图片描述

用法其实很简单,例如,使用最常见的“/p”既然是前置,就有机会替换个别基础类的实现。

我们一般可以用下面方法获取父加载器,但是通常,调用getParent()返回的都是null

2. 扩展类加载器 (Extension or Ext Class-Loader)

负责加载jre/lib/ext下的jar包,就是所谓的extension机制。该目录也可以设置java.ext.dirs来覆盖

java -Djava.ext.dirs=your_ext_dir HelloWorld

3.应用类加载器(Application or App Class-Loader)

加载classpath。通常来说,其默认就是jdk内建的应用类加载器,但是它同样是可能被修改的,比如:

java -Djava.sysem.class.loader=com.yourcorp.YourClassLoader HelloWorld

如果指定了这个参数,那么jdk内建的应用类加载器就会成为定制加载器的父亲,这种方式通常用在类似需要改变双亲委派模式的场景。

在这里插入图片描述

通常被问到双亲委派的话,参考这个图是最好的情况。试想,如果不同类加载器都自己加载需要的某个模型,就会浪费性能了。

通常类加载机制有三个基本特征:

  • 双亲委派模型

虽然不是每个类加载器都遵守这个模型。但是有的时候,启动类加载器,需要加载用户代码,比如jdk内部的serviceLoader机制,用户就可以在标准框架中实现项目。

但是像,jdbc,file system,jndi就不会有双亲委派,而是用上下文加载器。

  • 可见性

子类加载器都可以访问父类的,不可逆。

  • 单一性

父加载器加载过的不会在子加载器重复加载。

自定义加载器

常见的场景有:

  1. 实现类似进程内隔离,达到模块化的效果,我们可以直接想到的SpringBoot了哈哈哈。
  2. 应用层需要从不同的dataSource获取数据
  3. 手动操纵修改字节码

于是我们可以将自定义类加载过程理解为:

  1. 通过指定名称,找到其二进制实现
  2. 创建Class对象,完成类加载过程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值