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

前面的文章已经介绍过很多关于JVM、线程方面的相关知识了,这是都是在面试中很容易被问到的,之后还会介绍GC调优,分布式事务,SQL锁等相关知识。希望大家可以在一起多交流交流,能够多学一点东西。

今天介绍一下类加载过程和双亲委派模型。

概述

一般来说,Java类加载过程分为三个主要步骤:加载、链接、初始化。接下来,对这三个方面简要介绍一下:

1、加载
是Java将字节码数据从不同的数据源中读取到JVM中的,并且映射为JVM可以识别的数据结构(Class对象),这里的数据源,可以是jar,可以是class文件,甚至是网络数据源;如果输入数据不是ClassFile的结构,就会抛出异常ClassFormatError。

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

2、链接
这是核心的步骤,简单来说就是把原始的类定义信息平滑的转入JVM运行的过程中,这里分为三步:

  • 验证,这是JVM安全的保障,JVM需要检验字节码的数据结构是否满足要求,不然就抛出VerifyError,这样就防止了恶意信息或者不合规的信息危害JVM的运行,验证阶段有可能触发更多的class加载。
  • 准备,创建类或接口的静态变量,并且初始化静态变量的初始值。但是这里的“初始化”和下面的显示初始化是有区别的,侧重点在于分配所需要的内存空间不会去执行更进一步的JVM指令。
  • 解析,在这一步将常量池中的符号引用替换为直接引用。

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

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

深入分析

准备阶段谈到静态变量,那么对于常量和不同静态变量有什么区别?
请看下面代码:

public static int a = 100;
public static final int INT_CONSTANT = 1000;
public static final Integer INTEGER_CONSTANT = Integer.valueOf(10000);

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

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

  • 如果要真正理解双亲委派模型,需要理解Java中类加载器的架构和职责,至少要懂得具体有哪些内建的类加载器;以及如何自定义类加载器。
  • 从应用角度,加入Java启动很慢,有没有办法尽量减少Java类加载的开销?

知识扩展

首先,从架构角度,一起来看看Java 8以前的各种类加载器的结构,下面是三种Oracle JDK内建的类加载器。

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

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

对于做底层开发的工程师,有的时候可能不得不去试图修改JDK的基础代码,也就是通常意义上的核心类库,我们可以使用下面的命令行参数。
在这里插入图片描述
用法其实很易懂,例如,使用最常见的 “/p”,既然是前置,就有机会替换个别基础类的实现。

我们一般可以使用下面方法获取父加载器,但是在通常的JDK/JRE实现中,扩展类加载器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. 实现类似进程内隔离,类加载器实际上用作不同的命名空间以提供类似容器、模块化的效果。
  2. 应用需要从不同的数据源获取类定义信息
  3. 需要自己操纵字节码,动态修改或者生成类型

总体上简单理解自定义类加载过程:

  1. 通过指定名称,找到二进制实现
  2. 创建Class对象,并完成类加载过程
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值