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虚拟机默认使用
这个父级类装载器来装载它所调用的其他类。由于一个父级类装载器不能委托子级类装载器进行装载类,因此一般情况下,一个已被父级类装载器装载的类不能够调
用只能被子级类装载器装载的其他类。
基本原理
所有类都由类装载器载入,载入内存中的类对应一个
java.lang.Class
实例。
已被加载的类由该类的类加载器实例与该类的全路径名的组合标识。设有
packagename.A Class
,分别被类加载器
CL1
和
CL2
加载,则系统中有两个不同的
java.lang.Class
实例:
<CL1, packagename.A>
和
<CL2, packagename.A>
。
存在一个
Bootstrap Loader
(以下简称为
BL
),由
C++
写成,负责在虚拟机启动后一次
性加载
Java
基础类库中的所有类。其他的类装载器由
Java
写成,都是
java.lang.ClassLoader
的子类。
除
BL
之外的所有类装载器都有一个
parent
属性,指向其父装载器。查阅
java.lang.ClassLoader
的源码,不难发现类装载器的委托装载方式。
throws
parent !=
.loadClass(name,
|
对于给定的类名,首先检查自己是否已加载过该类。如果没有,则首先通过父装载器加载(如果
parent==null
,则直接通过
BL
来加载,相当于
BL
是其父装载器)。如果父装载器也无法装载,才真正调用自己的
findClass()
方法来装载。
Java
基础类在
Java
虚拟机启动后由
BL
一次性载入。构成
Java
应用程序的其它类在程序运行过程中由不同类装载器按需通过
loadClass()
方法装载。
Java
程序启动过程中的类装载器
当执行“
java XXX.class
”时,
java.exe
首先找到
JRE
(
Java Runtime Environment
),接着找到位于
JRE
之中的
jvm.dll
,最后载入
jvm.dll
并启动虚拟机。
虚拟机一启动,先做一些初始化动作,如获取系统参数等,然后产生
BL
。
BL
加载
Java
基础类,这些类都存放在
JRE
中的
lib
目录下,可由
System.getProperty(“sun.boot.class.path”)
列出,如:
C:/Program C:/Program C:/Program C:/Program C:/Program C:/Program C:/Program |
BL
然后创建
sun.misc.Launcher$ExtClassLoader
(
ExtClassLoader
是定义于
sun.misc.Launcher
之内的内部类,继承自
java.lang.URLClassLoader
)的实例(以下简称
EL
)和
sun.misc.Launcher$AppClassLoader
(
AppClassLoader
是定义于
sun.misc.Launcher
之内的内部类,继承自
URLClassLoader
)的实例(以下简称
AL
),并将
EL
的
parent
属性设置为
null
,
AL
的
parent
属性设置为
EL
。
EL
在程序运行过程中按需加载保存在
JRE
的“
/lib/ext
”目录下的类。该目录可由
System.getProperty(“java.ext.dirs”)
读出,如
C:/Program |
AL
在程序运行过程中按需加载的类搜索路径则是从系统参数
java.class.path
取出的字符串。
java.class.path
是由我们在执行
java.exe
时,利用
-cp
或
-classpath
或
CLASSPATH
环境变量所决定。我们应用程序用到的非
JRE
提供类的搜索路径一般都配置在
java.class.path
中。
什么时候装载类,由什么类装载器装载
1.
Java
基础类由
BL
在虚拟机启动时一次性载入。
2.
包含
main()
的入口类由
AL
的
loadClass()
方法载入。
3.
由
new
关键字创建一个类的实例。该类由运行时刻包含该
new
语句的类实例的类装载器(
ClassLoader.getCallerClassLoader()
)的
loadClass()
方法载入。
4.
调用
Class.forName()
方法。完整的
forName()
函数版本有一个
ClassLoader
参数,用于指定用什么类装载器来装载指定类。
|
对于
public static Class<?> forName(String className)
版本,是由运行时刻包含该语句的类实例的类装载器(
ClassLoader.getCallerClassLoader()
)的
loadClass()
方法载入。
5.
调用某个
ClassLoader
实例的
loadClass()
方法。通过该
ClassLoader
实例的
loadClass()
方法载入。应用程序可以通过继承
ClassLoader
实现自己的类装载器。
6
.装载一个类时,首先要装载该类的基类及其接口。