JAVA类加载器
1.简介
Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。
1.1类加载的五个过程
1) 加载:根据查找路径找到相应的class文件,然后导入。类的加载方式分为
隐式加载
隐式加载指的是程序在使用new关键词创建对象时,会隐式的调用类的加载器把对应的类加载到jvm中。
显示加载
显示加载指的是通过直接调用class.forName()方法来把所需的类加载到jvm中。
2) 检查:检查夹加载的class文件的正确性。
3) 准备;给类中的静态变量分配内存空间。
(不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中。)
4) 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。
5) 初始化:对静态变量和静态代码块执行初始化工作。
类加载器的作用
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法
区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class
对象,作为方法区类数据的访问入口。
3.类加载的动态性体现
一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现
4.类加载器的层次结构
JVM中有3个默认的类加载器
1. 引导/启动(Bootstrp)类加载器
由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。
它是在Java虚拟机启动后初始化的,负责加载核心Java库,加载<JAVA_HOME>/jre/lib目录下的类(比如rt.jar)
2.扩展(Extensions)类加载器
该类由sun.misc.Launcher$ExtClassLoader实现。
主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。
3.应用程序(Application )类加载器
由sun.misc.Launcher$AppClassLoader实现
它根据 Java 应用的类路径(classpath,java.class.path 路径下的内容)来加载 Java 类。
一般来说,Java 应用的类都是由它来完成加载的。
4.自定义类加载器
原因
因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,
比如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。
自定义类加载器的实现步骤
1、继承java.lang.ClassLoader
2、重写父类的findClass方法
(JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。如没有特殊的要求,一般不建议重写loadClass搜索类的算法)
三者的关系(伦理上是父子关系,但是是组合的方式实现的)
使用三个类加载器的原因,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型。
5.类加载器之间是如何协调工作的
java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,即java是如何区分一个类该由哪个类加载器来完成呢。 在这里java采用了双亲委托机制,
双亲委托机制(代理模式的一种)
这个机制简单来讲就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
例子
public class Test {
public static void main(String[] args) {
ClassLoader c = Test.class.getClassLoader(); // 获取Test类的类加载器
System.out.println(c);
ClassLoader c1 = c.getParent(); // 获取c这个类加载器的父类加载器
System.out.println(c1);
ClassLoader c2 = c1.getParent();// 获取c1这个类加载器的父类加载器
System.out.println(c2);
}
}
结果
sun.misc.Launcher$AppClassLoader@6d06d69c
sun.misc.Launcher$ExtClassLoader@70dea4e
null
可以看出Test是由AppClassLoader加载器加载的,
AppClassLoader的Parent 加载器是 ExtClassLoader,
ExtClassLoader的Parent为 null
原因是前面有提到Bootstrap Loader是用C++语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java程序代码里试图打印出其内容时,我们就会看到输出为null。
6.为什么要使用这种双亲委托模式
因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。
为了保证 Java 核心库的类型安全。
• 这种机制就保证不会出现用户自己能定义java.lang.Object类的情况