Java程序并不是一个原生的可执行文件,而是由许多独立的类文件组成,每一个文件对应一个Java类。此外,这些类文件并非立即全部装入内存的,而是根据程序需要装入内存。ClassLoader专门负责类文件装入到内存。
在class文件中描述的各种信息,最终都需要被加载到虚拟机中之后,才能被运行和使用。虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换,解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
总结起来ClassLoader的作用:
(1)加载class文件进入JVM
(2)审查每个类应该由谁加载,采用双亲委托机制
(3)将class字节码重新解析成JVM要求的对象格式
1、JVM的四种类加载器
类加载器:通过一个类的全限定名来获取描述此类的二进制字节流,实现这个动作的代码模块称为“类加载器”。
从上图我们就可以看出类加载器之间的父子关系(注意不是类的集继承关系)。
Bootstrap ClassLoader:BootStrap 是最顶层的类加载器,它是由C++编写并且已经内嵌到JVM中了,主要用来读取Java的核心类库JRE/lib/rt.jar。
Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。
App ClassLoader(System ClassLoader):负责记载classpath中指定的jar包及目录中class。Custom ClassLoader:属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。
如果我们要实现自己的类加载器,不管是直接继承ClassLoader还是继承URLclassLoaderlei ,它的父加载器都是AppClassLoader,因为不管调用哪个父类构造器,创建的对象都必须最终调用getSystemClassLoader()作为父类加载器,而该方法获取的正是AppClassLoader。
如果应用中没有定义其他的类加载器,那么除了java.ext.dirs下的类是由ExtClassLoader来加载,其他的都是由AppClassLoader来加载。
下面问题就来了,JVM有四种类型的类加载器,java是如何区分一个类该由哪个类加载器来完成呢?
在这里java采用了双亲委托机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”。具体流程如下:
1、”A类加载器”加载类时,先判断该类是否已经加载过了;
2、如果还未被加载,则首先委托其”A类加载器”的”父类加载器”去加载该类,这是一个向上不断搜索的过程,当A类所有的”父类加载器”(包括bootstrap classloader)都没有加载该类,则回到发起者”A类加载器”去加载;
3、如果还加载不了,则抛出ClassNotFoundException。
2、ClassLoader抽象类的几个关键方法:
loadClass()
此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parent ClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法findLoadedClass()
此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。findClass()
此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。findSystemClass()
此方法负责从SystemClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如仍然为找到,则返回null。defineClass()
此方法负责将二进制的字节码转换为Class对象resolveClass()
此方法负责完成Class对象的链接,如已链接过,则会直接返回。Class.forName()与ClassLoader.loadClass()
这两方法都可以通过一个给定的类名去定位和加载这个类名对应的 java.lang.Class 类对象,区别如下:
初始化
Class.forName()会对类初始化,而loadClass()只会装载或链接。ClassLoader.loadClass()加载的类对象是在第一次被调用时才进行初始化的。可以利用上述的差异。比如,要加载一个静态初始化开销很大的类,就可以选择提前加载该类(以确保它在classpath下),但不进行初始化,直到第一次使用该类的域或方法时才进行初始化。类加载器可能不同
Class.forName(String)方法(只有一个参数),使用调用者的类加载器来加载, 也就是用加载了调用forName方法的代码的那个类加载器。当然,它也有个重载的方法,可以指定加载器。相应的,ClassLoader.loadClass()方法是一个实例方法(非静态方法)调用时需要自己指定类加载器,那么这个类加载器就可能是也可能不是加载调用代码的类加载器(调用代码类加载器通getClassLoader0()获得)
3、类的加载过程
类的加载要经过三步:装载(Load),链接(Link),初始化(Initializ)。其中链接又可分为校验(Verify),准备(Prepare),解析(Resolve)三步。
三个阶段:
(1)第一阶段:找到class文件并把这个文件包含的字节码加载到内存
(2)第二阶段:字节码验证,class类数据结构分析以及相应内存分配,符号表的链接。
(3)第三阶段:类中静态属性初始化赋值以及静态代码块的执行
总结起来,类从被加载到虚拟机内存开始,到卸载出内存为止,其生命周期包括:加载(loading),验证(verification),准备(preparation),解析(resolution),初始化(initialization)ÿ