类加载机制(三):类的加载与类加载器


title: 类加载机制(三):类的加载与类加载器
date: 2019-03-14 15:40:14
categories:

  • Java虚拟机
    tags:
  • 类加载器
  • 类加载机制

类的加载

引言

类的加载属于Java虚拟机类加载机制的第一个阶段,它的作用就是将二进制形式的Java类型加载到内存中去,最终形成的就是内存中的Class对象,这个对象封装了类的数据结构。而这个加载过程,就是由一个叫类加载器的程序所完成的,也就是说,一个Java类是由类加载器所加载到内存中的。

类的预先加载

在类的加载中,类加载器并不需要等到某个类被首次主动使用时再加载它。JVM规范允许类加载器在预料到某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError)错误。

如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

类加载器与双亲委托机制

顾名思义,类加载器就是用来把类加载到Java虚拟机中的。而从JDK1.2开始,类的加载就是遵循双亲委托机制来进行加载的,这种机制很好的保证了Java平台的安全与程序的有序。

类加载器

类加载器一共有两种类型:

Java虚拟机自带的加载器

----根类加载器(Bootstrap

----扩展类加载器(Extension

----系统(应用)加载器(System

用户自定义的类加载器

----java.lang.classLoader的子类

----用户可以定制类的加载方式

虚拟机自带加载器
  • 根(Bootstrp)类加载器(启动类加载器):负责加载的虚拟机的核心类库,比如 java.lang.*等。根类加载器是从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器本身的实现依赖于底层操作系统,属于虚拟机实现的一部分,它并没有实现java.lang.classLoader类。
  • 扩展类加载器(Extension):负责加载JDK的安装目录的jre\lib\ext子目录下的类库,从系统属性java.ext.dirs所指定的目录中加载类库。扩展类加载器是纯Java类,它继承于java.lang.classLoader类。
  • 系统(System)类加载器(应用类加载器):从环境变量classPath或者从系统属性java.class.path所指定的目录下加载类,它是用户自定义类加载器的默认父加载器。系统类加载器是纯Java类,它继承于java.lang.classLoader类。
自定义类加载器

有时想对字节码文件进行加密处理,使用Java虚拟机自带的加载器就无法加载已经加了密的字节码文件,这时就可以使用自定义加载器对加密的字节码文件进行解密,就可以将其还原为正确的字节码文件 。

双亲委托机制

Java虚拟机的类加载阶段,Java程序一般是由上述的类加载器一起配合着加载的。而这些类加载器它们之间配合着加载类采用的是一种叫双亲委托机制的加载机制。除了根类加载器以外,每一个类加载器都有一个父类加载器。

双亲委托机制的基本流程是这样:当一个类接收到类的加载请求时,它首先不会对其进行加载,而是交给它的父加载器Parent来进行尝试加载,如果这个父加载器不能对这个类进行加载,就继续往上传递,一直到根类加载器为止,如果最后根类加载器也无法加载,又会一级级的传递下来,让适合的类加载器来进行加载。

如上图所示,自定义类加载器Loader1想要加载Test类,它不会先去加载这个类,而是一级级的往上传递,到达根类加载器后,若根类记载器无法加载,再一级级的传递下来,最后才Loader1来完成加载。

需要指出的是,加载器之间的父子关系实际上指的是加载器对象之间的包装关系,而不是类之间的继承关系。一对父子加载器可能是同一个类加载器类的两个不同实例,也可能不是。在子加载器对象中还包装了一个父加载器对象(请看后续博客:类加载机制(四):解析ClassLoader)。

代码测试
NO.1
   public static void main(String[] args) {
       	//获取到系统类加载
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
       //打印出各类加载器
       System.out.println(classLoader);
        while (null != classLoader){
            //获取此类加载器的父加载器
            classLoader = classLoader.getParent();
            System.out.println(classLoader);
        }
    }

输出结果:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@4554617c
null

启动类加载器,即根类加载器为null

NO.2
public class MyTest12 {
    public static void main(String[] args) {
        String[] strings = new String[1];
        MyTest12[] myTest12s = new MyTest12[1];
        int[] ints = new int[1];
        System.out.println(strings.getClass());
        System.out.println(strings.getClass().getClassLoader());
        System.out.println("----------");
        System.out.println(myTest12s.getClass());
        System.out.println(myTest12s.getClass().getClassLoader());
        System.out.println("----------");
        System.out.println(ints.getClass());
        System.out.println(ints.getClass().getClassLoader());

    }
}

输出结果:

class [Ljava.lang.String;
null
----------
class [LclassLoader.MyTest12;
sun.misc.Launcher$AppClassLoader@18b4aac2
----------
class [I
null

前文已说过,基本数组类的class类型直接继承于java.lang.Object,从上可以看出,component类型为基本数据类型的数组是由启动类加载器所加载的,而引用类型的数组则是由系统类加载器加载的。

双亲委托机制的好处

考虑到章节的完整性,先po出双亲委托机制的好处,关于命名空间导致类不相同的问题,请看后续博客:类加载机制(五):自定义类加载器与深入双亲委托机制

  • 可以确保Java核心类库的类型安全,让Java类随着它的类加载器一起具备了一种带有优先级的层次关系:比如,所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object这个类会被加载到Java虚拟机中;如果这个加载过程是由各自的类加载器去加载的话,那系统中会产生多个版本的Object类,这些类位于不同的命名空间中,相互之间不兼容,不可见,应用程序将会变得混乱。而通过双亲委托机制,Java核心类库中的类都由启动加载器来完成加载,从而保证了Java应用使用的都是同一个Java核心类库,它们之间是相互兼容的。

  • 可以确保Java核心类库所提供的类不会被自定义的类所替代。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值