热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ClassLoading---(类装载机制,开发者不得

http://blog.csdn.net/totodo/archive/2005/01/28/271798.aspx 也许你认为Cla


http://blog.csdn.net/totodo/archive/2005/01/28/271798.aspx




也许你认为Class Load是一个高级话题,不管怎样,作为开发者你还是要了解它。



本文基于最新得JDK5,然而将诉的内容却包含了最基本的原理,希望你能更加深入了解自己所使用得语言。


 

 






理解CLassLoader



 



如果你自己定义了一个 org.test.Object 。




你在程序中这样写:




import ort.test.Object




Object o = new String(); 


也许你欣然以为这样写没问题,但实际上你错了。 




这样会报

ClassCastException

,





一个Class在JVM中得标识是由它得 PackAge 和 类名决定得(也就是它的名称空间)所决定的。




 org.test.Object 并不等同于  java.lang.Object



 



在java中,每个类都是java.lang.Class得实例。所有的类都可以这样自定义:




java.lang.Class klass = Myclass.class;







而实例化一个类,可以是:Myclass myclass = new Myclass() 也可以是: myclass.newInstance();




 

 



在JVM中。所有得类都由

java.lang.ClassLoader

.以及它的子类加载的,我们在运行程序的时候,首先要从 JAVA_HOME/jre/rt.jar开始。  不过我们发现JDK文档里并没有介绍bootstrap.jar。 实际上bootstrap 是JDK之外得,它得方式和JVM的是不一样的。




JDK中除了ClassLoader可以加载类之外,还有以下这些也可以。







  • java.net.URLClassLoader








  • java.security.SecureClassLoader








  • java.rmi.server.RMIClassLoader








  • sun.applet.AppletClassLoader








Class Loaders工作原理






 





  除了bootstrap之外,所有的Classloader都有个父类Class Loader ,他们都是instanceof

java.lang.ClassLoader



 



看看JDK1.5中的一个例子




假如有个方法loadClass


 



protected synchronized Class loadClass


    (String name, boolean resolve)


    throws ClassNotFoundException{




    // First check if the class is already loaded


    Class c = findLoadedClass(name);


    if (c == null) {


        try {


            if (parent != null) {


                c = parent.loadClass(name, false);


            } else {


                c = findBootstrapClass0(name);


            }


        } catch (ClassNotFoundException e) {


            // If still not found, then invoke


            // findClass to find the class.


            c = findClass(name);


        }


    }


    if (resolve) {


    resolveClass(c);


    }


    return c;


}







 





那设置它父类的方式有两种。




public class MyClassLoader extends ClassLoader{


    public MyClassLoader(){


        super(MyClassLoader.class.getClassLoader());


    }


}




或者




public class MyClassLoader extends ClassLoader{


    public MyClassLoader(){


        super(getClass().getClassLoader());


    }


}


 



这里是首选第一种。





因为getClass()是在构造函数内部得方法,所以必须要 有构造函数代码存在,但是如果不存在,那就找父类得classloader 一直往上找,直至找到到findBootstrapClass, 如果它也不存在得话,那时候findClass()方法会被调用执行。。 (那时候会报一个ClassNotFoundException)






 




来看看findClass()得代码







    protected Class findClass(String name)


        throws ClassNotFoundException {


        throw new ClassNotFoundException(name);


    }







 







在findClass() 方法内 class loader要取得到得字节码(就是编译后*.class文件里得内容),也不一定就是.class文件, 这些字节码可以来自本地,也可以是系统,网络(借着这个你可以理解一下Cobra,RMI),也可以是用




BCEL



(Apache一个基于字节码得一个引擎库)

...等等。 一但字节码找到了。那时候就开始执行




defineClass()方法。那时候ClassLoader便会定义出一个类来。


 


每个ClassLoader出来的类都是不同的, 如果有两个ClassLoader载入两各相同的程序,defineClass()定义得两个类也是不同得。详细请看(



Java language specification










 




 



 

 




下面有幅图画 展示了一个MyMainclass.class是如何装载执行的。(由多个classLoader加载同一个Target.class),----




根据上面得解析,既然由两个classLoader()载入Target.class得字节码 ,那defineClass()就会产生两个class的定义。








 



所以很容易得出以下结论:


Target target1 = (Target) target2; 是不正确的 。target1 和 target2是由两个不同的classloader定义的。




 

 

 



Class Loading ---(类装载机制,开发者不得不知道的故事)


 



具体请看

Inside Class Loaders

(Andreas Schaefer)












 




 我们是否需要自定义的ClassLoader?




 



    理由之一: 如果我们自定义了ClassLoader,那我们便可以控制JVM的加载动作了。


  


   上面说一个class标识是由于package+classname组成得。 对于所有实现java.io.

Serializable

接口的类,都是由

serialVersionUID

管理这些类得版本(

RMI,JNDI,Security里都有这样一个ID)

。它用64位的Hash来表示 (这个Hash由classname,filed,method组成)。从技术上讲如果classname,field,mehtod所构成的Hash都一样,那就会认为是同一个版本。






  假设有这样一个情况,我们要写一个java 执行引擎(比如:用一个RMI 发布一个Server端程序,执行client的接口方法) ,  既然要能执行,那引擎肯定要实现有Client所特定任务的接口(这里为TaskIntf)。  一但任务提交给执行引擎,Server要做的第一件事情就是要装载所有要执行的代码。 假设不同的终端递交了不同的代码。而偏偏又都是同样的包名,和同样的类名。 那服务器能否会辨别到底是那个Client提交过来的执行请求?



 



  现在出个问题: 如果在服务器端一个执行程序执行两个客户端提交同一个版本得代码,如何才让客户端会得到预期的执行结果? 




  别以为这个很简单,下面先建个RMI玩玩。看看结果会是怎样。



 


 



  本地文件如下图。



 


 




Class Loading ---(类装载机制,开发者不得不知道的故事)






图 2 程序目录结构 (本文中含代码)。




 



在samepath目录下, 有着两个version.Version.class,他们得包名类名都一样,唯一不同的是。




v1目录中的方法是:



    public void fx(){


        log("this = " + this + "; Version.fx(1).");


    }





v2目录中的方法是:




    public void fx(){


        log("this = " + this + "; Version.fx(2).");


    }



 



执行一下看看:





set CLASSPATH=.;%CURRENT_ROOT%\v1;%CURRENT_ROOT%\v2


%JAVA_HOME%\bin\java Test 结果如下图





Class Loading ---(类装载机制,开发者不得不知道的故事)




图 3.

classPath得目录设为v1




 



切换到




set CLASSPATH=.;%CURRENT_ROOT%\v2;%CURRENT_ROOT%\v1


%JAVA_HOME%\bin\java Test


结果如下图:





Class Loading ---(类装载机制,开发者不得不知道的故事)




图 4.

classpath目录设为v2

 


很明显,上面的例子中能从classpath中找到先后次序。如果我们把v1,v2的version.Version。都删调。而把他们打成一个

myextension.jar包,放到java.ext.dirs目录下。。这时候就通过

ExtClassLoader


来装载了,而不是AppClassLoader.




结果会是如下:





Class Loading ---(类装载机制,开发者不得不知道的故事)




图 5.


AppClassLoader


and


ExtClassLoader





注意看



sun.misc.Launcher$ExtClassLoader@a9c85c



这说明是ExtClassLoader 加载了。

 


继续往下看,另外一个例子。 在

differentversions

目录下的例子,里面包含了RMI的ServerImpl这样一个执行引擎。Client实现了

common.TaskIntf接口。 两个 client.TaskImpl分别如下:



    static{


        log("client.TaskImpl.class.getClassLoader


        (v1) : " + TaskImpl.class.getClassLoader());


    }




    public void execute(){


        log("this = " + this + "; execute(1)");


    }


 另一个则是:


    


 static{


        log("client.TaskImpl.class.getClassLoader


        (v1) : " + TaskImpl.class.getClassLoader());


    }




    public void execute(){


        log("this = " + this + "; execute(2)");


    }




这样子来执行(顺序随便,这里把

%CURRENT_ROOT%\client2放在前面

):


CLASSPATH=%CURRENT_ROOT%\common;%CURRENT_ROOT%\server;


    %CURRENT_ROOT%\client2;%CURRENT_ROOT%\client1


%JAVA_HOME%\bin\java server.Server


先启动Server..


  分别把两个client提交给服务器执行, (即便执行程序中得client1.bat 和 client2.bat server监控屏幕如图6所示。) 



Class Loading ---(类装载机制,开发者不得不知道的故事)




图 6. Execution Engine Server console



 


再来看下面两个图(图7和图8),分别是client端得执行显示。









Class Loading ---(类装载机制,开发者不得不知道的故事)





图 7. Execution Engine Client 1 console


























Class Loading ---(类装载机制,开发者不得不知道的故事)




图 8. Execution Engine Client 2 console











 









纵观上面三次执行结果,发现由于服务器启动得时候使用了AppClassLoader.所以无论怎么样都是载入得是client2(因为client2的classpath次序比较在前),










这里client1 很郁闷,它在自己那执行明明是  execute(1) 通过 RMI 发送给服务器端执行就成了 execute(2)..










值得注意的是: 在client1,client2分别发送给服务器执行之后,服务器端显示的记录是:


client.TaskImpl.class.getClassLoader(v2):sun.misc.lancuher@AppClassLoader@xxxx zhiz只执行了一次。而



this=client.TaskImpl@xxxxx



execute(2);执行了两次










上面已经讲到过了,对于一个ClassLoader来讲 同样的page+className 只能定义一个 class,而不同的ClassLoader即便加载同一个page.className 也会定义不同的class










 









到这里,我才发现,解决上面提出得那个问题似乎并不容易。:(。










那如何解决呢?答案就是---使用自定义得classLoader ..










如果各位等不急的话, 先请看(目录中 differentversionspush 里面的代码)










很显然,我们很有必要写自定义的classloader.

















如何构造使用自定义的ClassLoader





既然自定义的ClassLoader,能解决上述问题,那接下去看看,我们如何来使用自定义的ClassLoader。










结合本文种的原码---(在differentversionspush的目录里),有个FileSystemClassLoader,类图描述如下:











Class Loading ---(类装载机制,开发者不得不知道的故事)




图9.











 









看看他的方法 findClassBytes(String className);










    public byte[] findClassBytes(String className){




        try{


            String pathName = currentRoot +


                File.separatorChar + className.


                replace('.', File.separatorChar)


                + ".class";


            FileInputStream inFile = new


                FileInputStream(pathName);


            byte[] classBytes = new


                byte[inFile.available()];


            inFile.read(classBytes);


            return classBytes;


        }


        catch (java.io.IOException ioEx){


            return null;


        }


    }




    public Class findClass(String name)throws


        ClassNotFoundException{




        byte[] classBytes = findClassBytes(name);


        if (classBytes==null){


            throw new ClassNotFoundException();


        }


        else{


            return defineClass(name, classBytes,


                0, classBytes.length);


        }


    }




    public Class findClass(String name, byte[]


        classBytes)throws ClassNotFoundException{




        if (classBytes==null){


            throw new ClassNotFoundException(


                "(classBytes==null)");


        }


        else{


            return defineClass(name, classBytes,


                0, classBytes.length);


        }


    }




    public void execute(String codeName,


        byte[] code){




        Class klass = null;


        try{


            klass = findClass(codeName, code);


            TaskIntf task = (TaskIntf)


                klass.newInstance();


            task.execute();


        }


        catch(Exception exception){


            exception.printStackTrace();


        }


    }












这 个类FileSystemClassLoader 被client使用了,用来定义class, 并且把它把client.TaskImpl(v1)转化为 byte[], 然后 byte[]发送到RMI Server执行。(上面讲了defineClass()能够执行任何字节码,来自编译后的文件,网络甚至是BCEL 字节码引擎库),   在Server端 ,又可以通过FileSystemClassLoader 以为byte[]的形式定义出 client.TaskImpl。









 









请看Client端的代码:










public class Client{




    public static void main (String[] args){




        try{


            byte[] code = getClassDefinition


                ("client.TaskImpl");


            serverIntf.execute("client.TaskImpl",


                code);


            }


            catch(RemoteException remoteException){


                remoteException.printStackTrace();


            }


        }




    private static byte[] getClassDefinition


        (String codeName){


        String userDir = System.getProperties().


            getProperty("BytePath");


        FileSystemClassLoader fscl1 = null;




        try{


            fscl1 = new FileSystemClassLoader


                (userDir);


        }


        catch(FileNotFoundException


            fileNotFoundException){


            fileNotFoundException.printStackTrace();


        }


        return fscl1.findClassBytes(codeName);


    }


}










在RMI服务器端ServerImpl 程序里, 接受到来自client的字节码(byte[]),于是FileSystemClassLoader 会从byte[]构造出一个class, 实例话,并且执行。




有一点要注意:每次接收到一个client的请求,FileSystemClassLoader都会重新实例化(执行结果中可以看出来),这就意 味着,client.Impl不在是在classpath中被找到的,而是通过FileSystemClassLoader 的findClass() 来执行deFineClass(),这样每次 FileSystemClassLoader 都是创建新的实例,,自然 deFine出来的class也是不同的。 这样,我们就能在RMI的执行中区分出 这两个class来。(client.TaskImpl != client.TaskImp  在上篇就已经得出结论了。 )














看看服务器端的执行代码:










public void execute(String codeName, byte[] code)throws RemoteException{




        FileSystemClassLoader fileSystemClassLoader = null;




        try{


            fileSystemClassLoader = new FileSystemClassLoader();


            fileSystemClassLoader.execute(codeName, code);


        }


        catch(Exception exception){


            throw new RemoteException(exception.getMessage());


        }


    }









 









服务器端的执行结果:











Class Loading ---(类装载机制,开发者不得不知道的故事)




图10,服务器端显示












下面两图分别是客户端显示的。












Class Loading ---(类装载机制,开发者不得不知道的故事)




图11. client1的执行显示















Class Loading ---(类装载机制,开发者不得不知道的故事)




图12. client2执行结果










 









哈,上面洋洋洒洒那么多,总算是一步一步的教会了大家 如何在同一个VM虚拟机中,执行“不同版本”的代码 。(这些代码有同样的类名和包名)。









 






Class Loaders 在 J2EE 中应用。












到这里你其实已经不足为奇下面一些东西了。。。


      我的一个A_war.war的web项目中 代码是 com.mycom.Test 而我在另外一个B_war.war的wenb项目中的 代码也是com.mycom.Test 而他们照样工作的好好的。


      当一个大型的 EJB项目,一台服务器上部署了多个 EJB,War工程时候,他们也不会互相影响。AppServer还会有自己的装载策略,比如你web中用的jar包,会优先于AppServer本身所带有的。














    另外,J2EE的ClassLoader机制更详细的能容你可以参考Tss上的这篇文章。



Understanding J2EE Application Server Class Loading Architectures










资源文件:






  • Sample code



    for this article





  • JDK 1.5 API Docs







  • The



    Java language specification







  • "



    Understanding Extension Class Loading



    " in the Java tutorial




  • "



    Inside Class Loaders



    " from



    ONJava







  • "



    Inside Class Loaders: Debugging



    " from



    ONJava







  • "



    What version is your Java code?



    " from



    JavaWorld







  • "



    Understanding J2EE Application Server Class Loading Architectures



    " from



    TheServerSide








  • Byte Code Engineering Library








  • Server-Based Java Programming



    by Ted Neward












  • 原文:





    http://www.onjava.com/pub/a/onjava/2005/01/26/classloading.htm







    Class Loading ---(类装载机制,开发者不得不知道的故事)




    推荐阅读
    • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
    • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
    • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
      本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
    • Spring Boot 应用中实现观察者模式的深度解析与实践
      在 Spring Boot 应用中,观察者模式是一种常用的设计模式,用于建立对象间的依赖关系。当某一对象的状态发生改变时,所有依赖该对象的对象会自动接收到通知并进行相应的更新。本文将深入探讨观察者模式的实现原理,并通过具体实例展示如何在 Spring Boot 项目中有效应用这一模式,以提高系统的解耦性和可维护性。 ... [详细]
    • 深入解析HTTP网络请求API:从基础到进阶的全面指南
      本文全面解析了HTTP网络请求API,从基础到进阶,详细介绍了Android平台上的两种原生API——HttpUrlConnection和HttpClient。这两种API通过对底层Socket的封装,提供了高效、灵活的网络通信功能。文章不仅涵盖了基本的使用方法,还深入探讨了性能优化、错误处理和安全性等方面的高级主题,帮助开发者更好地理解和应用这些工具。 ... [详细]
    • Spring框架的核心组件与架构解析 ... [详细]
    • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
    • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
      本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
    • Amoeba 通过优化 MySQL 的读写分离功能显著提升了数据库性能。作为一款基于 MySQL 协议的代理工具,Amoeba 能够高效地处理应用程序的请求,并根据预设的规则将 SQL 请求智能地分配到不同的数据库实例,从而实现负载均衡和高可用性。该方案不仅提高了系统的并发处理能力,还有效减少了主数据库的负担,确保了数据的一致性和可靠性。 ... [详细]
    • Presto:高效即席查询引擎的深度解析与应用
      本文深入解析了Presto这一高效的即席查询引擎,详细探讨了其架构设计及其优缺点。Presto通过内存到内存的数据处理方式,显著提升了查询性能,相比传统的MapReduce查询,不仅减少了数据传输的延迟,还提高了查询的准确性和效率。然而,Presto在大规模数据处理和容错机制方面仍存在一定的局限性。本文还介绍了Presto在实际应用中的多种场景,展示了其在大数据分析领域的强大潜力。 ... [详细]
    • 在日常的项目开发中,测试环境和生产环境通常采用HTTP协议访问服务。然而,从浏览器的角度来看,这种访问方式会被标记为不安全。为了提升安全性,当前大多数生产环境已经转向了HTTPS协议。本文将详细介绍如何在Spring Boot应用中配置SSL证书,以实现HTTPS安全访问。通过这一过程,不仅可以增强数据传输的安全性,还能提高用户对系统的信任度。 ... [详细]
    • Optimize Data Compression Prior to Transmission ... [详细]
    • Docker镜像加载优化:开发者的高效实践指南
      Docker镜像加载优化:开发者的高效实践指南 ... [详细]
    • 本课程深入探讨了 Python 中自定义序列类的实现方法,涵盖从基础概念到高级技巧的全面解析。通过实例演示,学员将掌握如何创建支持切片操作的自定义序列对象,并了解 `bisect` 模块在序列处理中的应用。适合希望提升 Python 编程技能的中高级开发者。 ... [详细]
    • 深入解析 Python 中的 NumPy 加法函数 numpy.add() ... [详细]
    author-avatar
    mrkly_825
    这个家伙很懒,什么也没留下!
    PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
    Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有