类加载器的收获

来源:互联网 发布:php数组拼接成字符串 编辑:程序博客网 时间:2024/06/11 15:09

类加载器的收获

最近读了很多关于ClassLoader的文章,收获不少。在这里赶紧总结回顾一下,吸收吸收。

首先,关于ClassLoader的一些基本的概念我就不再详细说了。如果连系统中不仅有一个ClassLoaderClassLoader的作用,ClassLoader是有层次结构,为什么要有自定义类加载器,这些问题都还不明白,那么下文读起来肯定很吃力。

 

ClassLoader这个东西本身是很简单的,就是加载类。类加载分两步,一步是定义和加载,对应着ClassLoaderloadClassdefine)方法,这个方法的作用是找到对应的Class文件,读取后,创建一个为这个类创建一个Class类的实例,并对字节码进行三遍验证;一个是初始化,这个进行的操作是将类的符号引用进行验证,对依赖的类进行加载,并且执行各种初始化方法(比如类中的static块)。经过这两步后,才能够真正的去使用一个类(调用方法、访其各个字段、创建对象等)。

ClassLoader之所以变的复杂,是因为在实际运用因为其层次关系和寻找的类路径不同而发生混淆。这主要发生在App Server上和一些使用代理的应用上。

这里有几点常识要提醒一下自己:

l         一个类在JVM中的类型是由类的全限定名+其所在的类加载器共同确定的。有一个不一样,JVM也会认为它们不是一个类,那么强制类型转换的时候就会出错。

l         每个 ClassLoader都有一个唯一的父ClassLoader,每次需要加载一个类的时候,使用委托的方式回溯递归到最根的ClassLoader(也就是启动类加载器,专门加载java核心类的),然后一层层的尝试去加载,也就是说,最先调用 loadClassClassLoader是最后一个去加载它的。

l         每个ClassLoader都有一个已经加载了的类的列表,基于以下两个事实:已经被一个ClassLoader加载了的类是不会这个ClassLoader被二次加载的;类加载采用委托模式。那么,子ClassLoader能够看到父ClassLoader和它自己加载的类,而无法看到其子ClassLoader和兄ClassLoader加载的类,这就是混淆的关键。

l         并不是调用loadClassMyClass)的那个类加载器加载了MyClass类,而是那个读取到了MyClass字节码并在调用defineClass的时候将这个字节码流作为参数传递,的ClassLoader加载了MyClass类,MyClass将保存在那个ClassLoader的已加载类列表中。

l         哪个ClassLoader会去主动加载一个类呢?Java规范规定,当执行代码段A的时候,如果需要加载一个类B,那么,就由加载AClassLoader去加载B。一段代码总要属于一个类吧,一个类总要被一个ClassLoader加载的吧,那么好了,加载关系弄清楚了^_^

有几种情况下我们需要注意ClassLoader的作用:

l         一个典型的App Server会为每一个应用创建一个ClassLoader,这样,每个应用的ClassLoader是兄弟关系,那么他们彼此之间加载的类信息是不可共享的,也就避免了应用间造成类使用混乱(尤其是在不同应用要使用不同版本的jar包时)。

l         还有就是各种应用接口的使用。如果需要动态的从数据源(比如说数据库、本地磁盘、网络、内存等)加载一个类,最好的方法是让这个类实现一个接口,将这个接口的class文件放在classpath目录下(或者别的目录,但是一定要保证这个类对AppClassLoader是可见的,这点很重要)然后用这个接口的引用指向这个类创建的实例。为什么要这么做呢?如果不这么做的话,你在程序中就只能够通过反射来访问这个动态记载的类的各种方法和字段了!为什么呢?因为AppClassLoader及其父ClassLoader无法加载这个你从自定义数据源加载的类,也就是说,在主程序中,根本就不知道这个自定义加载的类是什么,那么,就算你通过一些方法使得类编译通过,那么在运行时也会报错(ClassNotFoundException)。

l         上面的问题还有一个问题没有解决,就是如何在程序中创建ClassLoader并指定让它加载我们自定义的类呢?这里牵扯到一个争论很多的问题:Class.forName(String classNmae)Thread.currentThread().getContextClassLoader().loadClass(String classNmae)应该用哪个?哪个更好?关于这个,javaworld上有篇文章论述的非常好,叫做“Get a load of that name! ”,还有“Find a way out of the ClassLoader maze”对之也有很精妙的说明。在这里我说一下自己的认识。前者(Class.forName(String classNmae))其就是使用调用Class.forName 这个方法的类的ClassLoader去加载classNmae 这个类(这话说起来好别扭-_-!)。举例来说:如果A类在way方法中有一行是Class.forName”XXXX”),那么,forName方法会通过一个本地方法(Native Method)获得加载其调用者(也就是A类)的类加载器,然后用这个类加载器去加载XXXX类。后者(Thread.currentThread().getContextClassLoader().loadClass(String classNmae))其实是使用当前线程使用的ClassLoader去装载类。这个ClassLoader是在线程被创建的时候设置进去的,默认是继承父线程的ContextClassLoader这个值。这个问题搞清楚以后,就简单了,你可以按照自己的实际情况使用两者之一在程序中加载类。

l         关于类的更新。其实这里才是我关心ClassLoader的最初原因。一个类在被一个ClassLoader加载后,是不能够被更新或者卸载的。在Tomcat中,更新一个Servlet是通过创建一个新的ClassLoader,然后加载来实现的。同时Tomcat对应用中自定义的类也可以更新,但是这种更新的实现机制我还没有搞清楚,期望有和人讨论一下Tomcat的这个实现,我的msnDeepNightTwo@hotmail.com。这个问题欢迎大家来讨论http://www.jdon.com/jive/thread.jsp?forum=16&thread=27932

 

总的感觉:类加载器很简单,就好像画个横线很简单一样。但是怎么画在哪里画用什么画,都是问题。
原创粉丝点击