JVM类加载的过程

接下来了解Java虚拟机中类加载的全过程,即加载、验证、准备、解析、初始化这五个阶段所执行的操作。

一、类加载过程

类装载流程

image

1. 加载

加载是类装载的第一步,首先通过一个类的全限定名来获取定义此类的二进制字节流,并解析二进制流将里面的元数据(类型、常量等)载入到方法区,在java堆中生成对应的java.lang.Class对象。

2. 验证

验证的主要目的是确保Class文件的字节流中包含的信息符合《java虚拟机规范》的全部约束要求,也就是验证class文件的合法性。

  1. 版本号验证:比如class文件一定是以0xCAFEBABE开头的,另外对版本号也会做验证,例如如果使用java1.8编译后的class文件要再java1.6虚拟机上运行,因为版本问题就会验证不通过;
  2. 元数据验证:这个类是否被继承了不允许被继承的父类,类中的字段、方法是否与父类产生矛盾,方法重载是否符合要求等;
  3. 字节码验证:主要目的是通过数据流分析和控制流分析,确定程序语义是合法得、符合逻辑的。
  4. 符号引用验证:在将符号引用转化为直接引用的过程进行验证。比如根据字符串描述的全限定名是否能找到对应得类等;

3. 准备

准备阶段就是正式为类中定义的变量(静态变量、static修饰的变量)分配内存并设置类变量初始值的阶段。

例如:

public static int value=123;

这段代码在准备阶段value的值就会被初始化为0,只有到后面类初始化阶段时才会被设置为1。

但是对于static final(常量),在准备阶段就会被设置成指定的值,例如:

public static final  int value=123;

这段代码在准备阶段value的值就是123。

4. 解析

解析过程就是将符号引用替换为直接引用, 例如某个类继承java.lang.object,原来的符号引用记录的是“java.lang.object”这个符号,凭借这个符号并不能找到java.lang.object这个对象在哪里?而直接引用就是要找到java.lang.object所在的内存地址,建立直接引用关系,这样就方便查询到具体对象。解析分为:类和接口的解析、字段的解析、方法的解析。

5. 初始化

初始化过程,主要包括执行类构造方法、static变量赋值语句,staic{}语句块,需要注意的是如果一个子类进行初始化,那么它会事先初始化其父类,保证父类在子类之前被初始化。所以其实在java中初始化一个类,那么必然是先初始化java.lang.Object,因为所有的java类都继承自java.lang.Object。

说完了类加载过程,我们来介绍一下这个过程当中的主角:类加载器。

二、类加载器

通过一个类的全限定名去获取描述此类的二进制字节流的动作被称为类加载器。这个动作被放到了Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。

三、类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。也就是比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类是来源同一个class文件,只要加载它们的类加载器不同,那这两个类就必定不相等

这里所说的相等包括:class对象的equals()方法、isAssignableFrom()方法、isInstance()方法、使用instanceof关键字做对象所属关系判定等情况。

四、双亲委派模型

站在Java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassCloader),这个类加载器使用c++语言实现,是虚拟机自身的一部分;其他所有类加载器都算作另外一种,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.classLoader。

从Java开发人员的角度看,类加载器还可以划分的更细致一些,绝大部分Java程序都会使用到以下三种系统提供的类加载器:

  1. 启动类加载器(Bootstrap ClassCloader):这个类加载器负责将存放在<JAVA_HOME>\lib 目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中,启动类加载器无法被Java程序直接引用。

  2. 扩展类加载器(Extension ClassLoader):这个加载器由 sun.misc.launcher$ExtClassLoader 实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

  3. 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于这个类加载器是ClassLoader中的getSystemClassLoader() 方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

我们的应用程序都是由这三个类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。

上图展示的类加载器之间的这种层次关系。被称为类加载器的双亲委派模型。双亲委派模型要求除了最顶层的启动类加载器Bootstrap ClassLoader 外,其余所有类加载器都有父类加载器。这里的类加载器之间的父子关系,不是通过继承关系实现,而是通过组合关系实现的。

双亲委派的工作原理:

  1. 应用程序类加载器收到了类加载请求,首先不会自己尝试加载,而是调用父类扩展类加载器Extension ClassLoader去加载。

  2. 扩展类加载器收到类加载请求,首先不会自己尝试加载,而是继续调用父类启动类加载器Bootstrap ClassLoader去加载。因此所有的请求最终都应该传送到最顶层的启动类加载器去完成。

  3. 如果启动类加载器加载失败,例如在 <JAVA_HOME>/jre/lib 中没找到,会使用子类扩展类加载器Extension ClassLoader去加载。

  4. 若扩展类加载器Extension ClassLoader也加载失败,则会使用应用程序类加载器去加载。

  5. 若应用程序类加载器也加载失败,则会抛出ClassNotFoundException异常。

  双亲委派类加载模型的好处:

  • 对于程序的稳定性极为重要
  • 系统类防止内存中出现多份同样的字节码
  • 保证Java程序安全稳定运行

五、双亲委派模型的问题

顶层ClassLoader,无法加载底层ClassLoader的类

Java框架(rt.jar)如何加载应用的类?

比如:javax.xml.parsers包中定义了xml解析的类接口 Service Provider Interface SPI 位于rt.jar 
即接口在启动ClassLoader中。而SPI的实现类,在AppLoader。

这样就无法用BootstrapClassLoader去加载SPI的实现类。

解决

JDK中提供了一个方法: 1: Thread. setContextClassLoader();

用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题;
基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例,打通了双亲委派模型的层次结构来逆向使用类加载器。

六、双亲模式的破坏

双亲模式是默认的模式,但不是必须这么做;
Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent;
OSGi的ClassLoader形成网状结构,根据需要自由加载Class。

参考资料:

https://www.cnblogs.com/luckgood/p/8981508.html

《深入理解Java虚拟机》

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值