搞定JVM系列(五):Java的类加载机制和类加载器

类加载机制

类加载分为三个步骤:加载、连接(验证、准备、解析)、初始化

1、加载

加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。

在加载阶段,JVM需要完成3件事:

  • 通过类的全限定名获取该类的二进制字节流;
  • 将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
  • 在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2、连接

2.1 验证

验证阶段,确保加载进来的字节流符合JVM规范。将完成以下4个阶段的检验动作:

  • 文件格式验证
  • 元数据验证(是否符合Java语言规范)
  • 字节码验证(确定程序语义合法,符合逻辑)
  • 符号引用验证(确保下一步的解析能正常执行)

2.2 准备

准备阶段,为静态变量在方法区分配内存,并设置默认初始值。

2.3 解析

解析阶段,是虚拟机将常量池内的符号引用替换为直接引用的过程。

3、初始化

初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。

(注:当有父类且父类为初始化的时候,先去初始化父类;再进行子类初始化语句)

什么时候需要对类进行初始化?

  • 使用new该类实例化对象的时候;
  • 读取或设置类静态字段的时候(但被final修饰的字段,在编译器时就被放入常量池的静态字段除外static final);
  • 调用类静态方法的时候;
  • 使用反射Class.forName(“xxxx”)对类进行反射调用的时候,该类需要初始化;
  • 初始化一个类的时候,有父类,先初始化父类(注:1. 接口除外,父接口在调用的时候才会被初始化;2.子类引用父类静态字段,只会引发父类初始化);
  • 被标明为启动类的类(即包含main()方法的类)要初始化;
  • 当使用JDK1.7的动态语言支持时,如果一个java.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

以上情况称为对一个类进行主动引用,且有且只要以上几种情况需要对类进行初始化。

 

 

 

类加载器

类加载器实现的功能是:为加载阶段获取二进制字节流

JVM提供了以下3种系统的类加载器:

  • 启动类加载器(Bootstrap ClassLoader):最顶层的类加载器,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
  • 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  • 应用程序类加载器(Application ClassLoader):也叫做系统类加载器,可以通过getSystemClassLoader()获取,负责加载用户路径(classpath)上的类库。如果没有自定义类加载器,一般这个就是默认的类加载器。

类加载器之间的层次关系如下,

 

类加载器之间的这种层次关系叫做双亲委派模型

1、双亲委派模型

双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。

2、双亲委派模型的工作过程

如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。

只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。

3、双亲委派模型的代码实现

双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法当中。

  • 首先检查类是否被加载,没有则调用父类加载器的loadClass()方法;
  • 若父类加载器为空,则默认使用启动类加载器作为父加载器;
  • 若父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。

4、双亲委派机制

关于类加载器,我们不得不说一下双亲委派机制。听着很高大上,其实很简单。比如A类的加载器是AppClassLoader(其实我们自己写的类的加载器都是AppClassLoader),AppClassLoader不会自己去加载类,而会委ExtClassLoader进行加载,那么到了ExtClassLoader类加载器的时候,它也不会自己去加载,而是委托BootStrap类加载器进行加载,就这样一层一层往上委托,如果Bootstrap类加载器无法进行加载的话,再一层层往下走。

5、为何要双亲委派机制

为何要采用双亲委派机制呢?了解为何之前,我们先来说明一个知识点:判断两个类相同的前提是这两个类都是同一个加载器进行加载的,如果使用不同的类加载器进行加载同一个类,也会有不同的结果。

如果没有双亲委派机制,会出现什么样的结果呢?比如我们在rt.jar中随便找一个类,如java.util.HashMap,那么我们同样也可以写一个一样的类,也叫java.util.HashMap存放在我们自己的路径下(ClassPath).那样这两个相同的类采用的是不同的类加载器,系统中就会出现两个不同的HashMap类,这样引用程序就会出现一片混乱。

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值