- 浏览: 531002 次
- 性别:
- 来自: 西安
文章分类
- 全部博客 (233)
- 设计模式 (1)
- WEBLOGIC (2)
- spring (2)
- struts (1)
- hibernate (8)
- SOA (11)
- j2ee (0)
- corba (0)
- linux (2)
- oracle (0)
- webService (17)
- javaMail (1)
- junit (3)
- java (45)
- 正则表达式 (7)
- java+xml (4)
- html (2)
- javaScript (6)
- ajax (1)
- mysql (0)
- eclipse (2)
- eclipse插件开发 (3)
- 软件工程 (5)
- ant (6)
- 开源包 (6)
- 学习网站 (7)
- Struts2 (18)
- JSP (1)
- 持续集成 (4)
- FreeMaker (6)
- 工作项目点点 (4)
- Maven2 (6)
- JQuery (1)
- 日志记录 (9)
- 工作流 (4)
- BPEL (1)
- extJs (1)
- 组件 (1)
- OSGI (10)
- ESB总线学习 (1)
- 错误解决 (2)
- JMX (3)
- 网络知识 (1)
最新评论
-
duanlongk:
这个问题解决了么?
java.lang.NoClassDefFoundError: org/apache/tuscany -
sotrip:
从上面结果来看,HelloWorld.class是由虚拟机的内 ...
如何实现自己的classloader -
zengsir2008:
哎,不太明白。。。。
OSGI DS 实例 -
ralfsumahe:
...
使用XFire开发Web Service客户端完整入门教程 -
liuqiao_0702:
...
osgi spring hibernate =
Java类装载体系中的隔离性
正文
Java中类的查找与装载出现的问题总是会时不时出现在Java程序员面前,这并不是什么丢脸的事情,相信没有一个Java程序员没遇到过ClassNotException,因此不要为被人瞅见自己也犯这样的错误而觉得不自然,但是在如果出现了ClassNotFoundException后异常后一脸的茫然,那我想你该了解一下java的类装载的体制了,同时为了进行下面的关于类装载器之间的隔离性的讨论,我们先简单介绍一下类装载的体系结构。
1. Java类装载体系结构
装载类的过程非常简单:查找类所在位置,并将找到的Java类的字节码装入内存,生成对应的Class对象。Java的类装载器专门用来实现这样的过程,JVM并不止有一个类装载器,事实上,如果你愿意的话,你可以让JVM拥有无数个类装载器,当然这除了测试JVM外,我想不出还有其他的用途。你应该已经发现到了这样一个问题,类装载器自身也是一个类,它也需要被装载到内存中来,那么这些类装载器由谁来装载呢,总得有个根吧?没错,确实存在这样的根,它就是神龙见首不见尾的Bootstrap ClassLoader. 为什么说它神龙见首不见尾呢,因为你根本无法在Java代码中抓住哪怕是它的一点点的尾巴,尽管你能时时刻刻体会到它的存在,因为java的运行环境所需要的所有类库,都由它来装载,而它本身是C++写的程序,可以独立运行,可以说是JVM的运行起点,伟大吧。在Bootstrap完成它的任务后,会生成一个AppClassLoader(实际上之前系统还会使用扩展类装载器ExtClassLoader,它用于装载Java运行环境扩展包中的类),这个类装载器才是我们经常使用的,可以调用ClassLoader.getSystemClassLoader() 来获得,我们假定程序中没有使用类装载器相关操作设定或者自定义新的类装载器,那么我们编写的所有java类通通会由它来装载,值得尊敬吧。AppClassLoader查找类的区域就是耳熟能详的Classpath,也是初学者必须跨过的门槛,有没有灵光一闪的感觉,我们按照它的类查找范围给它取名为类路径类装载器。还是先前假定的情况,当Java中出现新的类,AppClassLoader首先在类传递给它的父类类装载器,也就是Extion ClassLoader,询问它是否能够装载该类,如果能,那AppClassLoader就不干这活了,同样Extion ClassLoader在装载时,也会先问问它的父类装载器。我们可以看出类装载器实际上是一个树状的结构图,每个类装载器有自己的父亲,类装载器在装载类时,总是先让自己的父类装载器装载(多么尊敬长辈),如果父类装载器无法装载该类时,自己就会动手装载,如果它也装载不了,那么对不起,它会大喊一声:Exception,class not found。有必要提一句,当由直接使用类路径装载器装载类失败抛出的是NoClassDefFoundException异常。如果使用自定义的类装载器loadClass方法或者ClassLoader的findSystemClass方法装载类,如果你不去刻意改变,那么抛出的是ClassNotFoundException。
我们简短总结一下上面的讨论:
1.JVM类装载器的体系结构可以看作是树状结构。
2.父类装载器优先装载。在父类装载器装载失败的情况下再装载,如果都装载失败则抛出ClassNotFoundException或者NoClassDefFoundError异常。
那么我们的类在什么情况下被装载的呢?
2. 类如何被装载
在java2中,JVM是如何装载类的呢,可以分为两种类型,一种是隐式的类装载,一种式显式的类装载。
2.1 隐式的类装载
隐式的类装载是编码中最常用得方式:
A b = new A();
如果程序运行到这段代码时还没有A类,那么JVM会请求装载当前类的类装器来装载类。问题来了,我把代码弄得复杂一点点,但依旧没有任何难度,请思考JVM得装载次序:
package test; Public class A{ public void static main(String args[]){ B b = new B(); } } class B{C c;} class C{}
揭晓答案,类装载的次序为A->B,而类C根本不会被JVM理会,先不要惊讶,仔细想想,这不正是我们最需要得到的结果。我们仔细了解一下JVM装载顺序。当使用Java A命令运行A类时,JVM会首先要求类路径类装载器(AppClassLoader)装载A类,但是这时只装载A,不会装载A中出现的其他类(B类),接着它会调用A中的main函数,直到运行语句b = new B()时,JVM发现必须装载B类程序才能继续运行,于是类路径类装载器会去装载B类,虽然我们可以看到B中有有C类的声明,但是并不是实际的执行语句,所以并不去装载C类,也就是说JVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类,这一点和编译类是不相同的。
2.2 显式的类装载
使用显示的类装载方法很多,我们都装载类test.A为例。
使用Class类的forName方法。它可以指定装载器,也可以使用装载当前类的装载器。例如:
Class.forName("test.A"); 它的效果和 Class.forName("test.A",true,this.getClass().getClassLoader()); 是一样的。
使用类路径类装载装载.
ClassLoader.getSystemClassLoader().loadClass("test.A");
使用当前进程上下文的使用的类装载器进行装载,这种装载类的方法常常被有着复杂类装载体系结构的系统所使用。
Thread.currentThread().getContextClassLoader().loadClass("test.A")
使用自定义的类装载器装载类
public class MyClassLoader extends URLClassLoader{ public MyClassLoader() { super(new URL[0]); } } MyClassLoader myClassLoader = new MyClassLoader(); myClassLoader.loadClass("test.A");
MyClassLoader继承了URLClassLoader类,这是JDK核心包中的类装载器,在没有指定父类装载器的情况下,类路径类装载器就是它的父类装载器,MyClassLoader并没有增加类的查找范围,因此它和类路径装载器有相同的效果。
我们已经知道Java的类装载器体系结构为树状,多个类装载器可以指定同一个类装载器作为自己的父类,每个子类装载器就是树状结构的一个分支,当然它们又可以个有子类装载器类装载器,类装载器也可以没有父类装载器,这时Bootstrap类装载器将作为它的隐含父类,实际上Bootstrap类装载器是所有类装载器的祖先,也是树状结构的根。这种树状体系结构,以及父类装载器优先的机制,为我们编写自定义的类装载器提供了便利,同时可以让程序按照我们希望的方式进行类的装载。例如某个程序的类装载器体系结构图如下:
图2:某个程序的类装载器的结构
解释一下上面的图,ClassLoaderA为自定义的类装载器,它的父类装载器为类路径装载器,它有两个子类装载器ClassLoaderAA和ClassLaderAB,ClassLoaderB为程序使用的另外一个类装载器,它没有父类装载器,但有一个子类装载器ClassLoaderBB。你可能会说,见鬼,我的程序怎么会使用这么复杂的类装载器结构。为了进行下面的讨论,暂且委屈一下。
3. 奇怪的隔离性
我们不难发现,图2中的类装载器AA和AB, AB和BB,AA和B等等位于不同分支下,他们之间没有父子关系,我不知道如何定义这种关系,姑且称他们位于不同分支下。两个位于不同分支的类装载器具有隔离性,这种隔离性使得在分别使用它们装载同一个类,也会在内存中出现两个Class类的实例。因为被具有隔离性的类装载器装载的类不会共享内存空间,使得使用一个类装载器不可能完成的任务变得可以轻而易举,例如类的静态变量可能同时拥有多个值(虽然好像作用不大),因为就算是被装载类的同一静态变量,它们也将被保存不同的内存空间,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很简单,编写自定义的类装载器。类装载器的这种隔离性在许多大型的软件应用和服务程序得到了很好的应用。下面是同一个类静态变量为不同值的例子。
package test; public class A { public static void main( String[] args ) { try { //定义两个类装载器 MyClassLoader aa= new MyClassLoader(); MyClassLoader bb = new MyClassLoader(); //用类装载器aa装载testb.B类 Class clazz=aa.loadClass("testb. B"); Constructor constructor= clazz.getConstructor(new Class[]{Integer.class}); Object object = constructor.newInstance(new Object[]{new Integer(1)}); Method method = clazz.getDeclaredMethod("printB",new Class[0]); //用类装载器bb装载testb.B类 Class clazz2=bb.loadClass("testb. B"); Constructor constructor2 = clazz2.getConstructor(new Class[]{Integer.class}); Object object2 = constructor2.newInstance(new Object[]{new Integer(2)}); Method method2 = clazz2.getDeclaredMethod("printB",new Class[0]); //显示test.B中的静态变量的值 method.invoke( object,new Object[0]); method2.invoke( object2,new Object[0]); } catch ( Exception e ) { e.printStackTrace(); } } }
//Class B 必须位于MyClassLoader的查找范围内, //而不应该在MyClassLoader的父类装载器的查找范围内。 package testb; public class B { static int b ; public B(Integer testb) { b = testb.intValue(); } public void printB() { System.out.print("my static field b is ", b); } }
public class MyClassLoader extends URLClassLoader{ private static File file = new File("c:\\classes "); //该路径存放着class B,但是没有class A public MyClassLoader() { super(getUrl()); } public static URL[] getUrl() { try { return new URL[]{file.toURL()}; } catch ( MalformedURLException e ) { return new URL[0]; } } }
程序的运行结果为:
my static field b is 1 my static field b is 2
程序的结果非常有意思,从编程者的角度,我们甚至可以把不在同一个分支的类装载器看作不同的java虚拟机,因为它们彼此觉察不到对方的存在。程序在使用具有分支的类装载的体系结构时要非常小心,弄清楚每个类装载器的类查找范围,尽量避免父类装载器和子类装载器的类查找范围中有相同类名的类(包括包名和类名),下面这个例子就是用来说明这种情况可能带来的问题。
假设有相同名字却不同版本的接口 A,
版本 1: package test; Intefer Same{ public String getVersion(); } 版本 2: Package test; Intefer Same{ public String getName(); }
接口A两个版本的实现:
版本1的实现 package test; public class Same1Impl implements Same { public String getVersion(){ return "A version 1";} } 版本2的实现 public class Same 2Impl implements Same { public String getName(){ return "A version 2";} }
我们依然使用图2的类装载器结构,首先将版本1的Same和Same的实现类Same1Impl打成包same1.jar,将版本2的Same和Same的实现类Same1Impl打成包same2.jar。现在,做这样的事情,把same1.jar放入类装载器ClassLoaderA的类查找范围中,把same2.jar放入类装器ClassLoaderAB的类查找范围中。当你兴冲冲的运行下面这个看似正确的程序。
实际上这个错误的是由父类载器优先装载的机制造成,当类装载器ClassLoaderAB在装载Same2Impl类时发现必须装载接口test.Same,于是按规定请求父类装载器装载,父类装载器发现了版本1的test.Same接口并兴冲冲的装载,但是却想不到Same2Impl所希望的是版本2 的test.Same,后面的事情可想而知了,异常被抛出。
我们很难责怪Java中暂时并没有提供区分版本的机制,如果使用了比较复杂的类装载器体系结构,在出现了某个包或者类的多个版本时,应特别注意。
掌握和灵活运用Java的类装载器的体系结构,对程序的系统设计,程序的实现,已经程序的调试,都有相当大的帮助。希望以上的内容能够对您有所帮助。
发表评论
-
类加载机制转载
2009-06-23 09:58 2339Java中一共有四个类加载器,之所以叫类加载器,是程序要用到某 ... -
OSGI环境下配置log4j日志
2009-05-07 14:07 1402log4j相信大家都用过的 ... -
Java虚拟机类装载:原理、实现与应用
2009-05-07 09:50 1490Java虚拟机类装载:原理、实现与应用 By jo ... -
Java程序类加载完全揭密
2009-05-07 09:49 1073Java程序类加载完全揭密 ... -
Java的类装载器(Class Loader)和命名空间(NameSpace)
2009-05-07 09:48 1729Java的类装载器(Class Loader)和命名空间(Na ... -
了解 JAVA classloader
2009-05-07 09:21 1064什么是 ClassLoader? ... -
如何实现自己的classloader
2009-05-07 09:12 2021如何实现自己的classloader 如何实现自己的clas ... -
ClassLoader原理
2009-05-07 09:06 1003JVM规范定义了两种类型的类装载器:启动内装载器(bootst ... -
JDK1.5中的线程池(java.util.concurrent.ThreadPoolExecuto
2009-04-03 11:32 1290在多线程大师Doug Lea的 ... -
MD5
2009-03-17 09:17 1003使用 Java 生成 MD5 编码 MD5即 ... -
JAVA字符串处理函数列表一览
2009-03-14 16:48 2009Java中的字符串也是一连串的字符。但是与许多其他的计算机语言 ... -
StringTokenizer(字符串分析解析类型)(转)
2009-03-14 16:35 3109功效:将字符串以定界符为界,分析为一个个的token(可理解为 ... -
JAVA乱码分析
2009-03-14 16:33 2148UNICODE编码,它采用双字 ... -
java.util.UUID
2008-12-19 18:01 4935GUID是一个128位长的数字,一般用16进制表示。算法的核心 ... -
java应用程序中动态生成PDF文件
2008-12-11 17:05 1164很多应用程序要求动态 ... -
JAVA和JAVAC 命令行
2008-12-11 17:03 2579javac和java命令行中的-classpath选项这是个很 ... -
创建java的可执行文件
2008-12-11 16:59 1368Jar工具的作用是java程序中若干文件联合压缩到一个Jar包 ... -
深入对象复制与比较
2008-12-04 10:09 15061.对象的复制2.clone()的使用3.对象实例的比较/// ... -
解析Java对象的equals()和hashCode()的使用
2008-12-04 10:07 846在Java语言中,equals()和h ... -
Java基础:深入 JAVA对象的复制与比较
2008-12-04 10:06 8571.对象的复制2.clone()的使用3.对象实例的比较/// ...
相关推荐
java类装载介绍,介绍了java装载类的先后顺序
类装载器学习一、类加载器的基本概念 类装载器学习一、类加载器的基本概念 类装载器学习一、类加载器的基本概念
Java虚拟机类装载的原理及实现
Java类装载过程_.docx
类的动态装载机制是JVM的一...本文介绍了JVM中类装载的原理、实现以及应用,尤其分析了ClassLoader的结构、用途以及如何利用自定义 的ClassLoader装载并执行Java类,希望能使读者对JVM中的类装载有一个比较深入的理解。
Java虚拟机类装载111.docJava虚拟机类装载111.docJava虚拟机类装载111.doc
它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 ...
简单的介绍和描述Java虚拟机类装载:原理、实现与应用
摘要通过构建一个能够把Java类装载隔离到一个指定的jar文件中的类装载组件容器框架,你可以确保运行时刻会装载你期望的组件版本。Java的类装载框架强有力且具有灵活性。它允许应用程序存取类库而不必链接到静态的...
本例采用java编写的装载问题,采用的是FIFO队列形式,参考:算法设计与分析
java虚拟机装载类的原理,从基础上描述,让我们更清楚底层的运作
主要讲述Java的类装载器和命名空间,ClassLoader/parent delegation模型
[浅析J2EE应用服务器的JAVA类装载器]python回朔异常的模块.docx
类装载器是Java 平台上最神秘,也是最 有趣的一个组,通过类装载器,除了可以实现程序的动态性之外,更能够做 到 无 懈 可 击 的 安 全 性
classguard是防止java反编译工具的工具,它允许java类文件使用128bit的AES加密加密。AES密钥是随机生成的每次启动加密工具。解密是一个自定义的类装载器透明地做。这类装载器的主要部分是用C防止反编译等篡改。
Java虚拟机类装载:原理、实现与应用.doc
完整的代码。用java来来写的装载问题。
java jvm类装载器原理 介绍较为详细 大家可以参考
java算法分析与设计之集装箱装载问题源代码 算法作为计算机专业学生的必修课,同时也是软件开发过程中必备的编程思想,对学习研究计算机专业意义重大;正因为这门课程难,所以除了相关方面的书籍,网络资源少的可怜,...
近年来,企业和java开发人员针对java类文件的...所以文章采用JNI结合类加载器装载等java技术设计了一套不透明性较大的类 文件保护机制。并分析和测试了这套类文件保护机制的有效性与可靠性。