以前对类装载器总是一个模糊的概念,它是如何工作的,实现的机制是什么等等。今天看书刚好看到这一点,就索性记录下来吧。
jvm主要有三部分组成:类装载器,类校验器和类解释器。jvm对于每一个要使用的类首先的任务就是要将类的字节码数据装载进来 ,完成类的装载的功能的就是类装载器。类装载器根据要装载的类的类名来定位和装载类的字节码数据,然后在返回给jvm。通常的类装载器需要根据要装载的类的类名来创建一个.class文件,然后到本地文件系统中读取这个.class文件,再把读取到的数据传送给jvm。但是类装载器并不是要把读取到的字节码数据原封不动的传递给jvm,它需要将.class文件的内容转化位jvm能够接受的字节码数据。例如,在本地文件中,class文件是保存时以GB2312的编码方式保存的,而jvm要求unicode的编码方式,这就需要类装载器来进行转化。其实类装载本身也是以个java类,jvm允许java开发人员编写自己的类装载器,以便使用其他各种不同的特殊方式来进行类的装载。例如我们要对一个.class文件进行加密,防止别人来反编译。这就需要编写一个能进行解密的类装载器,产生一个正常的类字节码。所以我们可以这样理解,类装载器就是一个能够制造出字节码数据的制造器。
类装载器装载完类的字节码数据后,会将这些数据返回给jvm,然后jvm将这些字节码数据编译位可执行的代码存储在内存中,并将索引信息保存在HashTable中,其索引信息就是这个类的完整名称。当jvm 要用到这个类的时候,它就会根据类名作为索引信息在HashTable 查找相应信息,如果可执行代码已经存在,jvm就会从内存中调用可执行代码,否则它就会继续装载和编译要使用的类。
我们知道现实世界上的任何一种事物,无论是有形还是无形的,都可以用一个类来表示。但是我们不可否认的是java中的类也是一种事物,所以它同样也可以用一个java类来进行描述,这个类的名称就是Class。这个类包含了各种方法,用以操作它所描述的类。例如,Class类中有一个getName方法用以获取它所描述的类的名称。可以认为,类装载器装载字节码数据的过程就是创建一个Class类的实例对象,这个Class实例对象所封装的内容就是刚才被装载的字节码数据,也就是jvm对当前加载类编译后存储载内存中的可执行代码。要在程序中获得代表某个类的字节码数据的Class实例对象,可以用以下三种方法:1.类名,class 2.对象.getClass() 3.Class,forName(类名)
下面我们就着重来谈谈类装载器的实现机制:在一个java虚拟机中可以有多个类装载器,当java虚拟机要装载一个类时,它通过以下一些方式来选择类装载器。
(1)一个类装载器本身也是一个java类,所有,类装载器自身也需要被另外一个类装载器装载,这就出现了类似人类的第一位母亲是如何产生出来的问题,虽然人类的这个问题至今也没一个确切的答案,但java中的类装载器的这个问题却很容易解决。java 虚拟机中内嵌了一个称为Bootstrap的类装载器,它是用特定于本地操作系统的代码来实现的,属于java虚拟机的内核,这个Bootstrap类装载器不需要其他的类装载器来装载,它主要用于装载java核心包中的类(即rt.jar文件中的类)。java核心包中有另外两个类装载器,即ExtClassLoader装载器和AppClassLoader装载器,它们都是用java语言编写的java类,其中ExtClassLoader类装载器负责装载存放载<JaVA_HOME>/jre/lib/ext目录下的jar包中的类,AppClassLoader负责加载应用程序的启动执行类。在编译和运行java程序时,都会通过ExtClassLoader来加载<JDK安装主目录>jre\lib\ext目录下的jar包来搜索要加载的类。其实如果将servlet.jar包复制到该目录下,就不需要在classpath环境变量中指定了。
一个java虚拟机中的所有的类装载器都时采用父子关系的树形结构组织的 ,在实例化每个类装载器的时候,都需要为其指定一个父级装载器的实例对象,如果没有指定这个父级类对象,则以ClassLoader.getSystemClassLoader()方法返回一个系统级类装载器,这个系统级类装载器就是AppClassLoader类装载器,它是在getSystemClassLoader()第一次被调用时设置的,即应用程序启动的早期被设置的。
每个类装载器只能分别装载特定位置和目路的类,但是ClassLoader被设置成了委托级模式,使得每一个类装载器都可以委托它的父级类装载器去加载类,而让应用程序可以借助子级类装载器来查找和装载更多的类。ClassLoader会通过loadClass()方法来查找这个类是否被装载,如果没有被装载,则委托它的父级类装载器去装载,如果父级类装载也没能够装载这个类,子级类装载器才会通过findClass()方法去真正装载那个类。父级类装载器调用loadClass()方法去装载一个类时,它也是先查找父级类装载器,这样一直追加到Bootstrap类装载器,如果它也没能够装载,则会回推到最初的类装载器,如果也没能装载,此时就会报告一个ClassNotFoundException的异常。
一个类装载器只能创建某个类的一份类字节码数据,即只能创建一个Class实例对象,而不能为同样一个类创建多个Class实例对象。我们可以看出采用委托模式给类加载管理带来了明显的好处当父级类装载加载了某个类,那么子级类装载器就不要再装载这个类了,这样就可以避免 一个java虚拟机的多个类装载器为同一个类创建多个类字节码数据的情况啦 。只要开发人员在编写自己的类装载器的时候,不去覆盖loadClass()方法,而覆盖findClass()方法,这样就可以继续使用委托模式。我们要闹闹记住下面一段话:依据一个类特定存放位置,这个类最终只能够被一个类装载器来装载,对于一个已被父级类装载器装载的类来说,java虚拟机默认使用这个父级类装载器来装载它所调用的其他类。由于一个父级类装载器不能委托子级类装载器进行装载类,因此一般情况下,一个已被父级类装载器装载的类不能够调用只能被子级类装载器装载的其他类。