当前位置: 主页 > 论文库 > 计算机 > 计算机理论 >

关于Java类加载器的探讨

时间:2011-12-12 11:36 来源:www.lunwen163.com 作者:163论文网 点击:
【摘要】Java的类加载机制是Java技术体系中比较核心的部分,这种机制虽然不和开发人员直接接触,但是如果对其背后的机理有一定理解的话,有助于开发人员排查程序中出现的类加载失败等技术问题,对理解java虚拟机的连接模型和java语言的动态性都有很大帮助。 【关键字】:Java、类加载、类加载器

一、对加载原理的分析
首先我们要分析类的加载原理,java中默认有三种类加载器:引导类加载器;扩展类加载器;系统类加载器(也叫应用类加载器);
1、引导类加载器负责加载jdk中的系统类,这种类加载器都是用c语言实现的,在java程序中没有办法获得这个类加载器,对于java程序是一个概念而已,基本上不用考虑它的存在,像String,Integer这样的类都是由引导类加载器加载器的。
2、扩展类加载器负责加载标准扩展类,一般使用java实现,这是一个真正的java类加载器,负责加载jre/lib/ext中的类,和普通的类加载器一样,其实这个类加载器对我们来说也不是很重要,我们可以通过java程序获得这个类加载器。
3、系统类加载器,加载第一个应用类的加载器(其实这个定义并不准确,下面你将会看到),也就是执行java MainClass 时加载MainClass的加载器,这个加载器使用java实现,使用的很广泛,负责加载classpath中指定的类。
类加载器之间有一定的关系(继承关系),我们可以认为扩展类加载器的父加载器是引导类加载器,不过系统类加载器的父加载器一定是扩展类加载器,类加载器在加载类的时候会先给父加载器一个机会,只有父加载器无法加载时才会自己去加载。我们无法获得引导类加载器,因为它是使用c实现的,而且使用引导类加载器加载的类通过getClassLoader方法返回的是null.所以无法直接操作引导类加载器,但我们可以根据Class.getClassLoader方法是否为null判断这个类是否引导类加载器加载。
二、系统类加载器
一般都认为系统类加载器是加载应用程序第一个类的加载器,也就是java MainClass命令中加载MainClass的类加载器,这种说法虽然不是很严谨,但基本上是可以这样认为的,因为我们很少会改变引导类加载器和扩展类加载器的默认行为。应该说系统类加载器负责加载classpath路径中的而且没有被扩展类加载器加载的类。如果classpath中有这个类,但是这个类也在扩展类加载器的类路径,那么系统类加载器将没有机会加载它。
通过一下方法可以获得系统类加载器:假设TestClass是你定义的一个类
TestClass.class.getClassLoader()返回的就是系统类加载器,当然这种方法无法保证绝对正确,我们可以使用更简单而且一定正确的方式:ClassLoader.getSystemClassLoader()获得系统类加载器。我们知道ClassLoader是一个抽象类,所以系统类加载器肯定是ClassLoader的一个子类实现。我们来看看它是什么。ClassLoader.getSystemClassLoader().getClass() 的结果是class sun.misc.Lancher$AppClassLoader。可以看出这是SUN的一个基础实现,是一个内部类;我们在看看它的父类是什么:ClassLoader.getSystemClassLoader().getClass().getSuperclass();的
结果是:class java.net.URLClassLoader。这个是J2SE的标准类,它的父类是SecureClassLoader,而SecureClassLoader是继承ClassLoader的。现在整个关系应该很清楚,我们会看到几乎所有的ClassLoader实现都是继承URLClassLoader的。
三、扩展类加载器
扩展类加载器似乎是一个不起眼的角色,它负责加载Java的标准扩展,它其实就是一个普通的加载器。由于没有直接的途径获得扩展类加载器,所以我们可以间接的获得扩展类加载器:ClassLoader.getSystemClassLoader().getParent().getClass();其实是通过系统类加载器间接的获得了扩展类加载器,结果是:class sun.misc.Launcher$ExtClassLoader这个类和系统类加载器一样是一个内部类,而且定义在同一个类中。
它的父类是:ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();
可以看出结果也是class java.net.URLClassLoader。扩展类加载jre/lib/ext目录下的所有类,包括jar,目录下的所有类。
在测试环境下编写一个HelloWorld,放到jre/lib/ext/下的某个目录比如 jre/lib/ext/TestClass/HelloWorld.class然后在你classpath也设置一份到这个类的路径,结果执行java HelloWorld时,这个类是被扩展类加载器加载器的,可以这样证明
public static void main(String[] args){
    System.out.println("loaded by"+HelloWorld.class.getClassLoader().getClass());
 System.out.println("Hello World");}
结果可以得到class sun.misc.Launcher$ExtClassLoader,当然如果你把jre/lib/ext下TestClass这个目录删除,仍然可以运行,但是这样结果是class sun.misc.Lancher$AppClassLoader如果你不知道这个过程的话,假设在你扩展类路径下有一份classpath中的拷贝,或者是比较低的版本,当你使用新的版本时会发现没有起作用,知道这个过程你就不会觉得奇怪了。另外就是两个不同的类加载器是可以加载一个同名的类的,也就是说虽然扩展类加载器加载了某个类,系统类加载器是可以加载自己的版本的,但是现有的实现都没有这样做,ClassLoader中的方法是会请求父类加载器先加载的,如果你自己定义类加载器完全可以修改这种默认行为,甚至可以让他没有父加载器。
对于扩展类加载器我们基本上不会去关心,也很少把你自己的jar放到扩展路径,大部分情况下我们都感觉不到它的存在,当然如果你一定要放到这个目录下,一定要知道这个过程,它会优先于classpath中的类。
四、加载器以及它们之间的关系
现在我们应该很清楚知道某个类是哪个加载器加载的,并且知道为什么是它加载的,如果要在运行时获得某个类的类加载器,直接使用Class的getClassLoader()方法就可以了。用户定义的类一般都是系统类加载器加载的,我们很少直接使用类加载器加载类。因为类在使用时会被自动加载,当用到某个类时该类会被自动加载,比如new TestA()会导致类TestA自动被加载,不过这种加载只发生一次。我们也可以使用系统类加载器手动加载类,ClassLoader提供了这个接口ClassLoader.getSystemClassLoader().loadClass("classFullName");这就很明确的指定了使用系统类加载器加载指定的类,但是如果该类能够被扩展类加载器加载,系统类加载器还是不会有机会的。我们最常用的还是使用Class.forName加载使用的类,这种方式没有指定某个特定的ClassLoader,会使用调用类的ClassLoader。也就是说调用这个方法的类的类加载器将会用于加载这个类。比如在类TestA中使用Class.forName加载类TestB,那么加载类TestA的类加载器将会用于加载类TestB,这样两个类的类加载器是同一个。
最后讨论一下如何获得某个类加载器加载了哪些类,可以看出哪些类被加载了。其实这个也不难,因为ClassLoader中有一个classes成员变量就是用来保存类加载器加载的类列表,而且有一个方法void addClass(Class c) { classes.addElement(c);}这个方法被JVM调用。我们只要利用反射获得classes这个值就可以了,不过classes声明为private的,我们需要修改它的访问权限:
classes = ClassLoader.class.getDeclaredField("classes");
classes.setAccessible(true);
List ret=(List) classes.get(cl); //classes是一个Vector
可惜的是对于引导类加载器没有办法获得加载的类,因为它是c实现的,在java中很难控制了。

五、【参考文献】
[1] 李发致 编著 《Java面向对象程序设计教程》 清华大学出版社  2009年11月
[2] 李长云,何频捷,李玉龙 编著 《软件动态演化技术》北京大学出版社2007年11月
[3] 麻志毅 编著《面向对象分析与设计》  机械工业出版社  2008年03月
[4] 孙卫琴 编著 《JAVA面向对象编程》  电子工业出版社  2006年07月