本文原标题是总结,但是呢,看了一下深入理解JVM内容后,还是too young too simple,改名为简要总结。
此文总结一下Java的类加载机制。
概述
总所周知,Java是跨平台的一种语言,究其原因是JVM虚拟机的跨平台。因此不论是何种语言,只要能编译成class字节码,就可以在JVM中运行。因此可以这样说,Java是跨平台的语言,而JVM是跨语言的平台。那么,就会有一个问题,JVM是如何加载class文件的呢(这里的class文件可以保存在任何地方,数据库,网络,内存等)?今天就来总结一下Java的类加载机制。
类的生命周期
简单总结一下类的生命周期,非常简单的总结,一眼见到底的那种
【图不会上传】
如图所示,class从加载到虚拟机开始到卸除内存位置,其生命周期包括加载加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,这其中验证、准备、解析统称为连接。
加载
将class字节码加载进内存
连接
验证
验证加载class字节码是否符合要求,比如是否语法错误,格式是否正确,有没有错误的重写父类的final方法等一系列复杂的验证
准备
这个阶段是为static变量赋默认值,直接给final常量赋最终值,也就是指定的值
解析
将符号引用转换为直接引用,直接引用就是指向一个内存地址
初始化
为static变量赋指定的值,loadClass不会触发初始化,classForName如果指定不初始化也不会进行初始化
一般造成初始化的操作
使用new关键字了,这时候肯定要进行初始化的嘛,因为都已经在使用中了
子类对象实例化 这个也很好理解,就是子类实例创建的时候肯定得先找到父类吧,要不怎么初始化,因为它包含父类的一些信息
使用该类的静态变量 看看初始化干了啥(当然,不仅仅是这些,还有其他操作),不就是将静态变量初始了
使用
正常使用,用来创建对象啥的
卸载
一般不卸载,除非该类不存任何实例
加载该类的ClassLoader已经被回收。
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
类加载器
概述(深入理解JVM原话):
Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)。
类加载器可以说是Java语言的一项创新,它是早期Java语言能够快速流行的重要原因之一。类加载器最初是为了满足Java Applet的需求而设计出来的,在今天用在浏览器上的Java Applet技术基本上已经被淘汰[1],但类加载器却在类层次划分、OSGi、程序热部署、代码加密等领域大放异彩,成为Java技术体系中一块重要的基石,可谓是失之桑榆,收之东隅。
其实我理解的就是加载类的一种东西或者说是工具,这说的也很明显-类加载器,意思就是加载类的工具
类与类加载器
对于任意的类,其在内存中是由这个本身以及加载他的类加载器组成。这里有个值得注意的的点,如果两个类来源于同一个class文件,但是
呢,不是由同一个类加载器加载,那么两个类就不相等
这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。如果没有注意到类加载器的影响,在某些情况下可能会产生具有迷惑性的结果,代码清单7-8中演示了不同的类加载器对instanceof关键字运算的结果的影响。
双亲委派模型
【图不会上传】
如上图,Java中一共有三层类加载器,分别是启动类加载器,扩展类加载器以及应用类加载器,还有最后的自定义类加载器
启动类加载器负责加载\lib目录或者是被-Xbootclasspath参数所指定的路径中。而且可以是Java虚拟机能够识别的类库加载到虚拟机中。启动类加载器用户无法直接应用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,那直接使用null代替即可,代码清单7-9展示的就是java.lang.ClassLoader.getClassLoader()方法的代码片段,其中的注释和代码实现都明确地说明了以null值来代表引导类加载器的约定规则。
扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。根据“扩展类加载器”这个名称,就可以推断出这是一种Java系统类库的扩展机制,JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能,在JDK 9之后,这种扩展机制被模块化带来的天然的扩展能力所取代。由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
应用程序类加载器(Application Class Loader):这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
自定义类加载器,只需要重写ClassLoader里的findClass()方法就可以了,这个可以实现class的加解密
源码展示:
protected synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// 首先,检查请求的类是否已经被加载过了
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器抛出ClassNotFoundException
// 说明父类加载器无法完成加载请求
}
if (c == null) {
// 在父类加载器无法加载时
// 再调用本身的findClass方法来进行类加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
类加载器的调用过程
先看一下缓存中有没有,也就是有没有加载过,要是加载过就直接返回了。如果没有就给父加载器,(这里的父加载器的意思不是继承二十合成复用,在类加载器内部里有个成员变量指向了父加载器)如果父类没有找到再给子类去加载。这种加载机制有点责任链的味道
双亲委派模型的好处
避免存在多个相同的类,例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都能够保证是同一个类。反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的ClassPath中,那系统中就会出现多个不同的Object类,Java类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。
也就是使系统更安全
破坏双亲委派模型
有时候吧,双亲委派模型也不怎么好,比如tomcat,作为一个web容器,肯定可以部署很多的web项目,这时候呢就会有不同版本的jar包被引用,这个时候呢,就需要进行破坏了要不然只有一个类被加载了.
破坏双亲委派模型很简单,只需要重写loadClass()方法,因为双亲委派就在这里实现。
至此,总结完毕