classloader 文章集錦1

http://pingfang.javaeye.com/blog/35319

一 。深入了解Java的ClassLoader机制

为了深入了解Java的ClassLoader机制,我们先来做以下实验:

package java.lang;
public class Test {
public static void main(String[] args) {
char[] c = "1234567890".toCharArray();
String s = new String(0, 10, c);
}
}

String类有一个Package权限的构造函数String(int offset, int length, char[] array),按照默认的访问权限,由于Test属于java.lang包,因此理论上应该可以访问String的这个构造函数。编译通过!执行时结果如下:

Exception in thread "main" java.lang.SecurityException: Prohibited package name:
java.lang
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$100(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClassInternal(Unknown Source)

奇怪吧?要弄清为什么会有SecurityException,就必须搞清楚ClassLoader的机制。

Java的ClassLoader就是用来动态装载class的,ClassLoader对一个class只会装载一次,JVM使用的ClassLoader一共有4种:

启动类装载器,标准扩展类装载器,类路径装载器和网络类装载器。

这4种ClassLoader的优先级依次从高到低,使用所谓的“双亲委派模型”。确切地说,如果一个网络类装载器被请求装载一个java.lang.Integer,它会首先把请求发送给上一级的类路径装载器,如果返回已装载,则网络类装载器将不会装载这个java.lang.Integer,如果上一级的类路径装载器返回未装载,它才会装载java.lang.Integer。

类似的,类路径装载器收到请求后(无论是直接请求装载还是下一级的ClassLoader上传的请求),它也会先把请求发送到上一级的标准扩展类装载器,这样一层一层上传,于是启动类装载器优先级最高,如果它按照自己的方式找到了java.lang.Integer,则下面的ClassLoader都不能再装载java.lang.Integer,尽管你自己写了一个java.lang.Integer,试图取代核心库的java.lang.Integer是不可能的,因为自己写的这个类根本无法被下层的ClassLoader装载。

再说说Package权限。Java语言规定,在同一个包中的class,如果没有修饰符,默认为Package权限,包内的class都可以访问。但是这还不够准确。确切的说,只有由同一个ClassLoader装载的class才具有以上的Package权限。比如启动类装载器装载了java.lang.String,类路径装载器装载了我们自己写的java.lang.Test,它们不能互相访问对方具有Package权限的方法。这样就阻止了恶意代码访问核心类的Package权限方法。

end

二 .一个简单的自定义classloader的实现

一个简单的自定义classloader的实现
kert 原创  (参与分:57651,专家分:885)   发表:2002-07-18 17:55   更新:2002-07-18 20:05   版本:1.0   阅读:16355
<!--start of article content -->

很多时候人们会使用一些自定义的classloader ,而不是使用系统的class loader。大多数时候人们这样做的原因是,他们在编译时无法预知运行时会需要那些class。特别是在那些appserver中,比如tomcat,avalon-phonix,jboss中。或是程序提供一些plug-in的功能,用户可以在程序编译好之后再添加自己的功能,比如ant, jxta-shell等。定制一个classloader很简单,一般只需要理解很少的几个方法就可以完成。
一个最简单的自定义的classloader从classloader类继承而来。这里我们要做一个可以在运行时指定路径,加载这个路径下的class的classloader。
通常我们使用classloader.loadclass(string):class方法,通过给出一个类名,就会得到一个相应的class实例。因此只要小小的改动这个方法,就可以实现我们的愿望了。
源码:
  1. protected synchronized class loadclass(string name, boolean resolve)    throws classnotfoundexception    {    
  2.     / first, check if the class has already been loaded    
  3.     class c = findloadedclass(name);    
  4.     if (c == null) {
  5.         try {
  6.            if (parent != null) {
  7.               c = parent.loadclass(name, false);
  8.            }else{
  9.               c = findbootstrapclass0(name);
  10.            }
  11.         }catch(classnotfoundexception e){
  12.             / if still not found, then call findclass in order
  13.             / to find the class.            
  14.            c = findclass(name);
  15.         }
  16.     }
  17.     if (resolve) {
  18.       resolveclass(c);
  19.     }
  20.     return c;
  21. }

source from classloader.java

first,check javaapi doc:上面指出了缺省的loadclass方法所做的几个步骤。
1.    调用findloadedclass(string):class 检查一下这个class是否已经被加载过了,由于jvm 规范规定classloader可以cache它所加载的class,因此如果一个class已经被加载过的话,直接从cache中获取即可。
2.    调用它的parent 的loadclass()方法,如果parent为空,这使用jvm内部的class loader(即著名的bootstrap classloader)。
3.    如果上面两步都没有找到,调用findclass(string)方法来查找并加载这个class。
后面还有一句话,在java 1.2版本以后,鼓励用户通过继承findclass(string)方法实现自己的class loader而不是继承loadclass(string)方法。
既然如此,那么我们就先这么做:)
  1. public class anotherclassloader extends classloader {
  2.     private string basedir;private static final logger log = 
  3.          logger.getlogger(anotherclassloader.class);    
  4.     public anotherclassloader (classloader parent, string basedir) {
  5.            super(parent);
  6.            this.basedir = basedir;
  7.     }
  8.     protected class findclass(string name)
  9.             throws classnotfoundexception {
  10.         log.debug("findclass " + name);
  11.         byte!#91;!#93; bytes = loadclassbytes(name);
  12.         class theclass = defineclass(name, bytes, 0, bytes.length);/a
  13.         if (theclass == null)
  14.             throw new classformaterror();
  15.         return theclass;
  16.     }
  17.     private byte!#91;!#93; loadclassbytes(string classname) throws
  18.         classnotfoundexception {
  19.         try {
  20.             string classfile = getclassfile(classname);
  21.             fileinputstream fis = new fileinputstream(classfile);
  22.             filechannel filec = fis.getchannel();
  23.             bytearrayoutputstream baos = new bytearrayoutputstream();
  24.             writablebytechannel outc = channels.newchannel(baos);
  25.             bytebuffer buffer = bytebuffer.allocatedirect(1024);
  26.             while (true) {
  27.                 int i = filec.read(buffer);
  28.                 if (i == 0 || i == -1) {
  29.                     break;
  30.                 }
  31.                 buffer.flip();
  32.                 outc.write(buffer);
  33.                 buffer.clear();
  34.             }
  35.             fis.close();
  36.             return baos.tobytearray();
  37.         } catch (ioexception fnfe) {
  38.             throw new classnotfoundexception(classname);
  39.         }
  40.     }
  41.     private string getclassfile(string name) {
  42.         stringbuffer sb = new stringbuffer(basedir);
  43.         name = name.replace('.', file.separatorchar) + ".class";
  44.         sb.append(file.separator + name);
  45.         return sb.tostring();
  46.     }
  47. }

[i]ps:这里使用了一些jdk1.4的nio的代码:)[/i]
很简单的代码,关键的地方就在a处,我们使用了defineclass方法,目的在于把从class文件中得到的二进制数组转换为相应的class实例。defineclass是一个native的方法,它替我们识别class文件格式,分析读取相应的数据结构,并生成一个class实例。

还没完呢,我们只是找到了发布在某个目录下的class,还有资源呢。我们有时会用class.getresource():url来获取相应的资源文件。如果仅仅使用上面的classloader是找不到这个资源的,相应的返回值为null。

同样我们看一下原来的classloader内部的结构。
  1. public java.net.url getresource(string name) {
  2.         name = resolvename(name);
  3.         classloader cl = getclassloader0();/这里
  4.         if (cl==null) {
  5.             / a system class.
  6.             return classloader.getsystemresource(name);
  7.         }
  8.         return cl.getresource(name);}


原来是使用加载这个class的那个classloader获取得资源。

  1. public url getresource(string name) {
  2.     url url;
  3.     if (parent != null) {
  4.         url = parent.getresource(name);
  5.     } else {
  6.         url = getbootstrapresource(name);
  7.     }
  8.     if (url == null) {
  9.         url = findresource(name);/这里
  10.     }
  11.     return url;
  12. }




这样看来只要继承findresource(string)方法就可以了。修改以下我们的代码:

  1. /新增的一个findresource方法
  2. protected url findresource(string name) {
  3.         log.debug("findresource " + name);
  4.         try {
  5.             url url = super.findresource(name);
  6.             if (url != null)
  7.                 return url;
  8.             url = new url("file://" + convername(name));
  9.             /简化处理,所有资源从文件系统中获取
  10.             return url;
  11.         } catch (malformedurlexception mue) {
  12.             log.error("findresource", mue);
  13.             return null;
  14.         }
  15. }
  16. private string convername(string name) {
  17.         stringbuffer sb = new stringbuffer(basedir);
  18.         name = name.replace('.', file.separatorchar);
  19.         sb.append(file.separator + name);
  20.         return sb.tostring();
  21. }


好了,到这里一个简单的自定义的classloader就做好了,你可以添加其他的调料(比如安全检查,修改class文件等),以满足你自己的口味:)

.jar文件?

end

三.tomcat reload,不得不说的故事

tomcat reload,不得不说的故事
kert 原创  (参与分:57509,专家分:885)   发表:2002-09-15 18:18   更新:2002-09-15 19:15   版本:0.5   阅读:10250
<!--start of article content -->

tomcat reload,不得不说的故事

[i]我们知道在使用tomcat时,如果设置了reload后,tomcat会自动侦测web-inf目录下修改过的资源。如果发现有变化(通常是依据文件的lastmodified值),便会自动重新载入所有 的资源。表面上看,似乎是个很好的主意:不用重新启动tomcat便可以更新我们的web应。尤其是在调试阶段,只需简单的更新我们的代码,就可以重新测试了。然而美丽的表面总是隐藏着不可测的秘密。[/i]
----------------
最近在使用tomcat时,就遇到了一个有趣的问题,简单当时困扰了我很久(也许是因为我比较苯:))。到这里和大家分享一下。

我在webapp应用中有一个daemon 线程,用来定时监视某个状态的改变。如果没有改变就sleep一段时间,否则进行某些相应的处理。类似如下的代码:
  1.  
  2. public class testreload{ 
  3.     private static final logger log = logger.getlogger(commonreload.class.getname());    
  4.     private testreload(){ 
  5.         log.info("constructing "+ getclass() + " : " + getclass().hashcode()); 
  6.         new thread(){ 
  7.             public void run(){ 
  8.                 while(true){ 
  9.                     work();/相应的处理工作 
  10.                     try
  11.                         sleep(10000); 
  12.                     }catch(throwable t){
  13.                     };
  14.                 } 
  15.             } 
  16.         }.start();
  17.    } 
  18.    private final static testreload instance = new testreload();
  19. }

[i]在constructor中构造这个线程,每隔10秒钟工作一次[/i]
这个类作为某个webapp中的一个组件,因此最初的入口还是一个servlet。当我为了debug,而重新编译代码并重新发布我的webapp后,发现原先生成的线程仍旧在工作,而同时tomcat也将新编译的代码载入内存,因此这时jvm中有了两个监视的线程在工作,因此会有不可预料的问题。但是这不仅仅是两个独立的工作线程的问题,虽然表面上如此。我修改了一下代码,添加了一个测试用的work方法,如下:
  1.  
  2. public class testreload{
  3.     ......
  4.     private void work(){
  5.     log.info("testreload "+
  6.            testreload.class.hashcode());
  7.       final classloader cl = getclass().getclassloader();
  8.       log.info("the class loader is " 
  9.           + cl.getclass().getname()+ " : " + cl.hashcode());       
  10.     }
  11. }

这里有三行输出信息,用来跟踪一些jvm内部的信息。
  1. log.info("testreload "+testreload.class.hashcode());

用来输出testreload的class的hashcode值。
  1. log.info("the class loader is " + cl.getclass().getname()+ " : " + cl.hashcode())

用来输出加载这个testclass的class的classloader的名字和hashcode。

然后用一个简单的servlet作为程序的入口:
  1.     
  2.     protected void doget(httpservletrequest httpservletrequest,
  3.                    httpservletresponse httpservletresponse)
  4.             throws servletexception, ioexception {
  5.         class reload = testreload.class;
  6.         writer w = httpservletresponse.getwriter();
  7.         w.write("working.../r/n");
  8.         w.write(reload.getname() + 
  9.           ": " + reload.hashcode());
  10.         w.flush();
  11.     }

这个servlet只是简单的要求classloader载入testclass的class并且进行class的初始化和相应的静态初始化。

我们来看一下试验的输出。
当webapp第一次运行时,屏幕输出入下:

......
2002-9-15 16:00:01 kert.reload.testreload 
信息: constructing class kert.reload.testreload : 2737550
2002-9-15 16:00:01 kert.reload.testreload work
信息: testreload 2737550
2002-9-15 16:00:01 kert.reload.testreload work
信息: the class loader is org.apache.catalina.loader.webappclassloader : 23414511
2002-9-15 16:00:11 kert.reload.testreload work
信息: testreload 2737550
2002-9-15 16:00:11 kert.reload.testreload work
信息: the class loader is org.apache.catalina.loader.webappclassloader : 23414511


我重新编译代码并发布后,tomcat reload相应的代码后并在此运行这个webapp:

......
2002-9-15 16:01:59 kert.reload.testreload 
信息: constructing class kert.reload.testreload : 9104244
2002-9-15 16:01:59 kert.reload.testreload work
信息: testreload 9104244
2002-9-15 16:01:59 kert.reload.testreload work
信息: the class loader is org.apache.catalina.loader.webappclassloader : 13754931
2002-9-15 16:02:01 kert.reload.testreload work
信息: testreload 2737550
2002-9-15 16:02:01 kert.reload.testreload work
信息: the class loader is org.apache.catalina.loader.webappclassloader : 23414511
2002-9-15 16:02:09 kert.reload.testreload work
信息: testreload 9104244
2002-9-15 16:02:09 kert.reload.testreload work
信息: the class loader is org.apache.catalina.loader.webappclassloader : 13754931
2002-9-15 16:02:11 kert.reload.testreload work
信息: testreload 2737550
2002-9-15 16:02:11 kert.reload.testreload work
信息: the class loader is org.apache.catalina.loader.webappclassloader : 23414511
......

可以很明显的看到,在tomcat reload后jvm中同时存在了两个工作线程。并且不仅仅如此,两个线程输出有着明显的不同。
  1. 两个testreload的class的hashcode不同,说明jvm内存中存在着两个不同的testreload的class的实例。
  2. 每个testreload的class的对应的classloader也不相同。

照理说,tomcat reload在reload一个webapp时,应该清除原先的所有载入的数据。包括已生成的对象和相应的class对象,然后交个gc来处理(回收所有的对象,包括class对象和classloader)。
但是由于有一个无法终止的线程,tomcat reload无法让线程停止,因此也无法回收相应的class。这样,在先前生成的所有class都会仍旧保存在内存中。并且与reload后的class同名,虽然由于加载的classloader不同,这两组class是无法互相访问的,因为他们属于不同的runtime package。

但是这种状况仍旧会导致很多问题。
  • 1.重复工作:有多个线程在做同样的工作。
  • 2.访问限制:由于runtime package的限制,原来在编译期互相可见的变量或是方法,在运行期可能无法互相访问。
  • 3.classnotfound:显而易见。

......

显然,tomcat reload并不能像我们想象的那样很够很好的完成我们的工作。虽然这不是tomcat的错,我猜想在其他的container中也会有这样的现象发生,如jboss。container并不能够终止我们的精灵线程,而我们也无法介入到container的reload机制中去,如何reload(remove)我们先前的代码。如果tomcat在reload之前,在remove旧的代码的时候可以定义一个回调函数,或是有一个event机制通知我们的应用,那么我们可以采取某些措施。

现在为止,我还没有想到一个比较好的方式来处理这种情况(还是比较笨的缘故)。暂时还是重启tomcat。或是把这个后台线程做成一个mbean,使用jmx来管理它。
如果各位有好解决方法或是相应的pattern,欢迎回贴。
如果各位发现某些错误,更是欢迎“砖头”。

 

相關評論:

评论人:crazycode    参与分: 101    专家分: 0 发表时间: 2003-01-14 14:42
你可以用single设计模式来保证只创建一个实例,再在启动线程时做一下判断,如果已经有线程在运行,中止它,再启动新的线程。
  评论人:kert    参与分: 57509    专家分: 885 发表时间: 2003-01-14 16:08
呵呵,由于代码是由tomcat的内置classloader加载,它们根本不再同一个runtime package中,
因此无法判断。
  评论人:quake_wang    参与分: 179    专家分: 130 发表时间: 2003-01-15 09:12
你可以把这个daemon线程的类放在tomcat的classpath下,而不是放在webapp下,这样应该就可以了.
  评论人:banq    参与分: 86    专家分: 10 发表时间: 2003-01-15 09:51
我不理解的问题是:

servlet一般情况下本质是线程的,你为什么还在其内部直接编制启动一个线程?
我觉得如果你可以在servlet容器外编制一个线程不停的访问servlet
这样可以实现你的目的
  评论人:banq    参与分: 86    专家分: 10 发表时间: 2003-01-15 10:09
如果一定要在servlet容器内部做,可以借鉴jive使用java.util.timertask
  评论人:wuyu    参与分: 80    专家分: 50 发表时间: 2003-02-14 16:49
精灵线程start的时候将其hashcode或时间戳记录在一个handle文件中,定期检查这个文件内容是否相符,如果不符则中止线程?
  评论人:panwen    参与分: 77    专家分: 0 发表时间: 2003-02-15 13:23
我觉得任何线程都应该有一个退出机制,例如:
public void run()
{
   while(flag)
  {
    try
    {
       work();
       thread.sleep(6000);
    }catch(exception e){}
  }
}

在servlet的destroy方法里
设置flag=false就可以让线程退出。
  评论人:fridaychen    参与分: 59    专家分: 10 发表时间: 2003-02-18 14:09
在tomcat中可以注册servletcontextlistener,这是一个标准的机制。

public void contextinitialized(servletcontextevent sce);
public void contextdestroyed(servletcontextevent sce);

允许程序在系统启动和关闭的时候作一些工作。我把线程的启动和关闭都放在这里了,这样系统在reload的时候,也会调用servletcontextlistener的方法。
  评论人:wuyu    参与分: 80    专家分: 50 发表时间: 2003-02-18 16:20

非常感谢楼上的所有朋友们,特别是fridaychen。

为了测试fridaychen所说的servletcontextlistener,我特意做了一个小测试,然后在tomcat4.1.18的/manager/html/list里面stop/start/reload /market这个web app,结果,在tomcat load webapp的时,contextinitialized中的代码被执行,系统开始定时执行继续了java.util.timertask的守护线程程序,stop时,contextdestroyed中的代码被执行,所有正在执行的守护线程程序均正常中止。

tomcat控制台的输出信息
startup init
start
d:/www/web/market_version/
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
timertask run...
destory

market.marketlistener源代码
package market;

/**
 * 侦听器程序测试
 */
public class marketlistener implements javax.servlet.servletcontextlistener {
    private java.util.timer timer;

    public marketlistener() {
        system.out.println( "startup init" );
        timer = new java.util.timer( true );
    }

    public void contextdestroyed( javax.servlet.servletcontextevent event ) {
        system.out.println( "destory" );
        timer.cancel();
    }

    public void contextinitialized( javax.servlet.servletcontextevent event ) {
   &

关键字:   ClassLoader    

一 、ClassLoader

 
1,   什么是 ClassLoader?
    Java 程序并不是一个可执行文件,是需要的时候,才把装载到 JVM中。ClassLoader 做的工作就是 JVM 中将类装入内存。 而且,Java ClassLoader 就是用 Java 语言编写的。这意味着您可以创建自己的 ClassLoader
    ClassLoader 的基本目标是对类的请求提供服务。当 JVM 需要使用类时,它根据名称向 ClassLoader 请求这个类,然后 ClassLoader 试图返回一个表示这个类的 Class 对象。 通过覆盖对应于这个过程不同阶段的方法,可以创建定制的 ClassLoader。
2, 一些重要的方法
A)  方法 loadClass
        ClassLoader.loadClass() 是 ClassLoader 的入口点。该方法的定义如下:
        Class loadClass( String name, boolean resolve );
         name  JVM 需要的类的名称,如 Foo 或 java.lang.Object。
         resolve 参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。
    
    B)  方法 defineClass
       defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 -- 它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成final的。

    C)  方法 findSystemClass
       findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。(Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。) 对于定制的 ClassLoader,只有在尝试其它方法装入类之后,再使用 findSystemClass。原因很简单:ClassLoader 是负责执行装入类的特殊步骤,不是负责所有类。例如,即使 ClassLoader 从远程的 Web 站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的,所以要 JVM 以缺省方式装入它们:从本地文件系统。这就是 findSystemClass 的用途。

     D) 方法 resolveClass
   正如前面所提到的,可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。


   E) 方法 findLoadedClass
      findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。

3, 怎么组装这些方法
  1) 调用 findLoadedClass 来查看是否存在已装入的类。
  2) 如果没有,那么采用那种特殊的神奇方式来获取原始字节。
  3) 如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
  4) 如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
  5) 如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
  6) 如果还没有类,返回 ClassNotFoundException。

4,Java 2 中 ClassLoader 的变动
1)loadClass 的缺省实现
   定制编写的 loadClass 方法一般尝试几种方式来装入所请求的类,如果您编写许多类,会发现一次次地在相同的、很复杂的方法上编写变量。 在 Java 1.2 中 loadClass 的实现嵌入了大多数查找类的一般方法,并使您通过覆盖 findClass 方法来定制它,在适当的时候 findClass 会调用 loadClass。 这种方式的好处是您可能不一定要覆盖 loadClass;只要覆盖 findClass 就行了,这减少了工作量。

2)新方法:findClass
     loadClass 的缺省实现调用这个新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代码,而无需要复制其它代码(例如,当专门的方法失败时,调用系统 ClassLoader)。

3) 新方法:getSystemClassLoader
     如果覆盖 findClass 或 loadClass,getSystemClassLoader 使您能以实际 ClassLoader 对象来访问系统 ClassLoader(而不是固定的从 findSystemClass 调用它)。
 
4) 新方法:getParent 
    为了将类请求委托给父代 ClassLoader,这个新方法允许 ClassLoader 获取它的父代 ClassLoader。当使用特殊方法,定制的 ClassLoader 不能找到类时,可以使用这种方法。
父代 ClassLoader 被定义成创建该 ClassLoader 所包含代码的对象的 ClassLoader。

 

二。Java ClassLoader

Java ClassLoader
 

这是一篇较早时候写的文章,最近在J道看到一个与classloader有关的讨论,于是重新翻出来。

静态库、动态连接库

程序编制一般需经编辑、编译、连接、加载和运行几个步骤。在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在连接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中。这种库称为静态库,其特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。

为了克服这个缺点可以采用动态连接库。这个时候连接器仅仅是在可执行文件中打上标志,说明需要使用哪些动态连接库;当运行程序时,加载器根据这些标志把所需的动态连接库加载到内存。

另外在当前的编程环境中,一般都提供方法让程序在运行的时候把某个特定的动态连接库加载并运行,也可以将其卸载(例如Win32的LoadLibrary()&FreeLibrary()和Posix的dlopen()&dlclose())。这个功能被广泛地用于在程序运行时刻更新某些功能模块或者是程序外观。

What is ClassLoader?

与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVM(Java虚拟机),然后再把Java class加载到JVM里头运行,负责加载Java class的这部分就叫做Class Loader。

JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java Class(即所有java.*开头的类)。另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由Bootstrap ClassLoader加载;其中Extension ClassLoader负责加载扩展的Java class(例如所有javax.*开头的类和存放在JRE的ext目录下的类),Application ClassLoader负责加载应用程序自身的类。

When to load the class?

什么时候JVM会使用ClassLoader加载一个类呢?当你使用java去执行一个类,JVM使用Application ClassLoader加载这个类;然后如果类A引用了类B,不管是直接引用还是用Class.forName()引用,JVM就会找到加载类A的ClassLoader,并用这个ClassLoader来加载类B。

Why use your own ClassLoader?

似乎JVM自身的ClassLoader已经足够了,为什么我们还需要创建自己的ClassLoader呢?

因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果编写你自己的ClassLoader,你可以做到:
1)在执行非置信代码之前,自动验证数字签名
2)动态地创建符合用户特定需要的定制化构建类
3)从特定的场所取得java class,例如数据库中
4) 等等

事实上当使用Applet的时候,就用到了特定的ClassLoader,因为这时需要从网络上加载java class,并且要检查相关的安全信息。

目前的应用服务器大都使用了ClassLoader技术,即使你不需要创建自己的ClassLoader,了解其原理也有助于更好地部署自己的应用。 

ClassLoader Tree & Delegation Model

当你决定创建你自己的ClassLoader时,需要继承java.lang.ClassLoader或者它的子类。在实例化每个ClassLoader对象时,需要指定一个父对象;如果没有指定的话,系统自动指定ClassLoader.getSystemClassLoader()为父对象。如下图:

在Java 1.2后,java class的加载采用所谓的委托模式(Delegation Modle),当调用一个ClassLoader.loadClass()加载一个类的时候,将遵循以下的步骤:
1)检查这个类是否已经被加载进来了?
2)如果还没有加载,调用父对象加载该类
3)如果父对象无法加载,调用本对象的findClass()取得这个类。

所以当创建自己的Class Loader时,只需要重载findClass()这个方法。

Unloading? Reloading?

当一个java class被加载到JVM之后,它有没有可能被卸载呢?我们知道Win32有FreeLibrary()函数,Posix有dlclose()函数可以被调用来卸载指定的动态连接库,但是Java并没有提供一个UnloadClass()的方法来卸载指定的类。

在Java中,java class的卸载仅仅是一种对系统的优化,有助于减少应用对内存的占用。既然是一种优化方法,那么就完全是JVM自行决定如何实现,对Java开发人员来说是完全透明的。

在什么时候一个java class/interface会被卸载呢?Sun公司的原话是这么说的:"class or interface may be unloaded if and only if its class loader is unreachable. Classes loaded by the bootstrap loader may not be unloaded."

事实上我们关心的不是如何卸载类的,我们关心的是如何更新已经被加载了的类从而更新应用的功能。JSP则是一个非常典型的例子,如果一个JSP文件被更改了,应用服务器则需要把更改后的JSP重新编译,然后加载新生成的类来响应后继的请求。

其实一个已经加载的类是无法被更新的,如果你试图用同一个ClassLoader再次加载同一个类,就会得到异常(java.lang.LinkageError: duplicate class definition),我们只能够重新创建一个新的ClassLoader实例来再次加载新类。至于原来已经加载的类,开发人员不必去管它,因为它可能还有实例正在被使用,只要相关的实例都被内存回收了,那么JVM就会在适当的时候把不会再使用的类卸载。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值