weblogic与Java类加载器原理试验解析

通过试验,得出一个结论,假设在Weblogic的Server/lib下有一个类,与应用的Webapp/WEB-INF/classes

下的类名相同,方法名也相同,仅有在后台打印出来的字符的稍许差别,那在Weblogic启动后,无论个文

件夹中的类谁是新编译的(版本新或旧),应用系统均默认是使用server/lib下的类,

而不是引用Webapp/WEB-INF/classes下的类。

一、通过翻阅大量的资料了解到,java类加载的原理如下

JVM在运行时会产生三个ClassLoader:

Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.其中,Bootstrap是用C++编写的,我们在Java中看不到它,是null,它用来加载核心类库。

关于Bootstrap ClassLoader,在JVM源代码中这样写道:

static const char classpathFormat[] =

"%/lib/rt.jar: "

"%/lib/i18n.jar: "

"%/lib/sunrsasign.jar: "

"%/lib/jsse.jar: "

"%/lib/jce.jar: "

"%/lib/charsets.jar: "

"%/classes ";

知道为什么不需要在classpath中加载这些类了吧?人家在JVM启动的时候就自动加载了,并且在运行过程中根本不能修改Bootstrap加载路径。

Extension ClassLoader用来加载扩展类,即/lib/ext中的类。

最后AppClassLoader才是加载Classpath的。

ClassLoader加载类用的是委托模型。即先让Parent类(而不是Super,不是继承关系)寻找,Parent找不到才自己找。看来ClassLoader还是蛮孝顺的。

三者的关系为:

AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。加

载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是AppClassLoader。

为什么要设计的这么复杂呢?其中一个重要原因就是安全性。

比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将

这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种

情况。因为要加载java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永

远没有机会加载。

有一篇文章完整诠释了Java类加载的原理,参见如下:

--------------------------------------------引用开始--------------------------------------

《我对java中类装载的理解》

1. Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中。

2. java中的类大致分为三种:

a.系统类

b.扩展类

c.由程序员自定义的类

3. 类装载方式,有两种:

a.隐式装载,程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中.

b.显式装载,通过class.forname()等方法,显式加载需要的类.

隐式加载与显式加载的区别:两者本质是一样?  ?

4. 类加载的动态性体现

一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是

先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了

内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时

再加载这也是java动态性的一种体现.

5.java类装载器

Java中的类装载器实质上也是类,功能是把类载入jvm中,值得注意的是jvm的类装载器并不是一个,而是三个,层次结构如下:

Bootstrap Loader - 负责加载系统类

|

- - ExtClassLoader - 负责加载扩展类

|

- - AppClassLoader - 负责加载应用类

为什么要有三个类加载器,一方面是分工,各自负责各自的区块,另一方面为了实现委托模型,下面会谈到该模型

6. 类加载器之间是如何协调工作的

前面说了,java中有三个类加载器,问题就来了,碰到一个类需要加载时,它们之间是如何协调工作的,

即java是如何区分一个类该由哪个类加载器来完成呢。

在这里java采用了委托模型机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent找不到,那么才由自己依照自己的搜索路径搜索类”,注意喔,这句话具有递归性。

下面举一个例子来说明,为了更好的理解,先弄清楚几行代码:

Public class Test{
  Public static void main(String[] arg){
	  ClassLoader c = Test.class.getClassLoader(); //获取Test类的类加载器
	  System.out.println(c);
	  ClassLoader c1 = c.getParent(); //获取c这个类加载器的父类加载器
	  System.out.println(c1);
	  ClassLoader c2 = c1.getParent();//获取c1这个类加载器的父类加载器
	  System.out.println(c2);
  }
} 

把以上代码存到d:\my 文件夹下,直接编译,然后在dos模式下运行

D:\my\java Test

。。。AppClassLoader。。。

。。。ExtClassLoader。。。

Null

D:\my

注: 。。。表示省略了内容

可以看出Test是由AppClassLoader加载器加载的,AppClassLoader的Parent加载器是ExtClassLoader,但

是ExtClassLoader的Parent为null是怎么回事呵,朋友们留意的话,前面有提到Bootstrap Loader是用C++

语言写的,依java的观点来看,逻辑上并不存在Bootstrap Loader的类实体,所以在java程序代码里试图打

印出其内容时,我们就会看到输出为null。

【注:以下内容大部分引用java深度历险】

弄明白了上面的示例,接下来直接进入类装载的委托模型实例,写两个文件,如下:

文件:Test1.java

Public class Test1{
  Public static void main(String[] arg){
	  System.out.println(Test1.class.getClassLoader());
	  Test2 t2 = new Test2();
	  T2.print();
  }
} 

文件: Test2.java

Public class Test2{
  Public void prin(){
  	System.out.println(this.getClass().getClassLoader());
  }
} 

这两个类的作用就是打印出载入它们的类装载器是谁,将这两个文件保存到d:\my目录下,编译后,我们

在复制两份,分别置于jdk1.4\jre\classes下(注意,刚开始我们的系统下没有此目录,需自己建立)与

jdk1.4\jre\lib\ext\classes下(同样注意,开始我们的系统下也没此目录,手工建立),然后切换到d:\my

目录下开始测试,

测试一:

<JRE所在目录>\classes下

Test1.class

Test2.class

<JRE所在目录>\lib\ext\classes下

Test1.class

Test2.class

D:\my下

Test1.class

Test2.class

dos下输入运行命令,结果如下:

D:\my>java Test1

Null

Null

D:\my>

从输出结果我们可以看出:

当AppClassLoader要载入Test1.class时,先请其Parent,也就是ExtClassLoader来载入,

而ExtclassLoader又请求其Parent,即Bootstrap Loader来载入Test1.class.

由于<JRE所在目录>\Classes目录为Bootstrap Loader的搜索路径之一,所以Bootstrap Loader找到了

Test1.class,因此将它载入,接着在Test1.class之内有载入Test2.class的需求,由于Test1.class是由

Bootstrap Loader所载入,所以Test2.class内定是由Bootstrap Loader根据其搜索路径来找,

因Test2.class也位于Bootstrap Loader可以找到的路径下,所以也被载入了,最后我们看到Test1.class

与Test2.class都是由Bootstrap Loader(null)载入。

测试二:

<JRE所在目录>\classes下

Test1.class

<JRE所在目录>\lib\ext\classes下

Test1.class

Test2.class

D:\my下

Test1.class

Test2.class

dos下输入运行命令,结果如下:

D:\my>java Test1

Null

Exception in thread “main” java.lang.NoClassdefFoundError:Test2 at Test1.main。。。

D:\my>

从输出结果我们可以看出:

当AppClassLoader要载入Test1.class时,先请其Parent,也就是ExtClassLoader来载入,

而ExtclassLoader又请求其Parent,即Bootstrap Loader来载入Test1.class.

由于 <JRE所在目录>\Classes目录为Bootstrap Loader的搜索路径之一,所以Bootstrap Loader找到了

Test1.class,因此将它载入,接着在Test1.class之内有载入Test2.class的需求,由于Test1.class是由

Bootstrap Loader所载入,所以Test2.class内定是由Bootstrap Loader根据其搜索路径来找,

但是因为Bootstrap Loader根本找不到Test2.class(被我们删除),而Bootstrap Loader又没有Parent,

所以无法载入Test2.class.最后我们看到Test1.class是由Bootstrap Loader(null)载入,而Test2.class则无法载入

测试三

<JRE所在目录>\classes下

Test2.class

<JRE所在目录>\lib\ext\classes下

Test1.class

Test2.class

D:\my下

Test1.class

Test2.class

dos下输入运行命令,结果如下:

D:\my>java Test1

。。。ExtClassLoader。。。

Null

D:\my>

从输出结果我们可以看出:

当AppClassLoader要载入Test1.class时,先请其Parent,也就是ExtClassLoader来载入,

而ExtclassLoader又请求其Parent,即Bootstrap Loader来载入Test1.class.

但是Bootstrap Loader无法在其搜索路径下找到Test1.class(被我们删掉了),所以ExtClassLoader只得

自己搜索,因此ExtClassLoader在其搜索路径 <JRE所在目录>\lib\ext\classes下找到了Test1.class,因

此将它载入,接着在Test1.class之内有载入Test2.class的需求,由于Test1.class是由ExtClassLoader

所载入,所以Test2.class内定是由ExtClassLoader根据其搜索路径来找,但是因为ExtClassLoader有

Parent,所以先由Bootstrap Loader帮忙寻找,Test2.class位于Bootstrap Loader可以找到的路径下,所

以被Bootstrap Loader载入了.最后我们看到Test1.class是由ExtClassLoader载入,而Test2.class则是由

Bootstrap Loader(null)载入

了解了以上规则,请朋友们自行分析以下场景的执行结果

测试四:

<JRE所在目录>\classes下

<JRE所在目录>\lib\ext\classes下

Test1.class

Test2.class

D:\my下

Test1.class

Test2.class

测试五:

<JRE所在目录>\classes下

<JRE所在目录>\lib\ext\classes下

Test1.class

D:\my下

Test1.class

Test2.class

测试六:

<JRE所在目录>\classes下

<JRE所在目录>\lib\ext\classes下

Test2.class

D:\my下

Test1.class

Test2.class

测试七:

<JRE所在目录>\classes下

<JRE所在目录>\lib\ext\classes下

D:\my下

Test1.class

Test2.class

-----------------------------------引用结束,感谢作者哈!----------------------------------

上述经过自己的猜测,并进行实地测试,发现确实有一定的道理。

二、Weblogic对于类加载的原理

容器也对类加载进行了封装,不过不同的容器对于类加载有所不同。在Weblogic中,是这样的原理。

Weblogic中classloader是分层次的,它只能加载比它层次高的类及它自身的类,同层次的类及比它层次低的类都不能加载。

在weblogic中的classloader有5个层次,从高到低排:

a.jdk

b.jdk ext

c.system classpath

d.(APP-INF/classes and APP-INF/lib)

e.(WEB-INF/classes and WEB-INF/lib)

注意:

这里是先加载classes中的类,再加载lib中的类,若要修改它的加载顺序,可以通过在Weblogic.xml(版本为8)中加入以下代码:

<container-descriptor>
	<prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>

f.ejb.jar

注意:e 和 f 的classloader是同级的。

所以APP-INF/lib和APP-INF/classes下类不能实例化webapp下的类,这点尤其要注意,否则会报类找不到的错误。

还有一篇网文《Weblogic10 Classloading 问题》比较详细地介绍了Weblogic加载类的原理。

来自Weblogic官方的说明文件中,对于Weblogic的类加载顺序给出了一个比较清晰和简单的描述:

当部署一个应用的时候,weblogic server会自动创建一个具有层次结构的类装载器。

a.Application Classloader负责装载应用中的所有的EJB JAR文件;

b.Web Application Classloader负责装载所有的Web application 中的WAR文件(所有得jsp文件除外);

c.Jsp Classloader 负责装载Web application 中的所有的jsp 文件;

Tomcat与Weblogic是相反的:

对于运行在 JavaEE 容器中的Web应用来说,类加载器的实现方式与一般的Java应用有所不同。不同的Web

容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。

该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。

这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自

己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java核心库的类是不在查找范围

之内的。这也是为了保证 Java 核心库的类型安全。

最后,总结一下,在Weblogic服务启动的过程中,自动形成一个具有层次结构的类装载器:

首先装载jdk及java扩展jar包或类;

然后再加载Weblogic本身使用的各个jar包或类;

然后再加载Web应用文件夹里面的classes下的类,

然后再加载Web应用文件夹里面lib下的jar包或类。

也就是说,每个层次的类装载器均对应不同的类路径,它们是一一对应的。

比如System装载器对应着jdk及扩展路径;

Application装载器对应着Weblogic的相关类;

而web应用装载器对应着webapp应用下的classes和lib下的路径;

而jsp装载器则对应着jsp文件。

当然,在加载过程中,若在高层次的加载器中已经加载了某类,那么再以后的加载中,再次遇到该类也不

会加载,只是会忽略。加载完成之后,将类放入Cache中供系统应用调用。

在系统的运行过程中,若遇到使用该类的情况,则会遵循先通过其父类加载器进行加载的原则,比方说,

我要加载一个WSWordManager类,则系统会首先在Cache中寻找,若找不到,则调用父装载器到与之对应的

路径里面去寻找,一直向上,找着了则进行加载,若找不着则报出ClassNotFound的异常。

----------------------------------------华丽的分隔符----------------------------------------

哈哈,自己耗时一天的试验也能够证明了这一点。

在解决系统项目“原生类重复加载,异常为jacob.dll already loaded in another classloader”问题时,

被迫研究了上述的原理。现在问题也算是较完美的解决了,更关键的是学到了很多相关底层的知识,对于

自己技术的提升是很有好处的。

时间: 2024-10-11 17:33:54

weblogic与Java类加载器原理试验解析的相关文章

Java类加载器 ClassLoader的解析

//参考 : http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类加载器基本概念 类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一.它使得 Java 类可以被动态加载到 Java 虚拟机中并执行.类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的.Java Applet 需要从远程下载 Java 类文件到浏览器中并执行.现在类加载器在 Web 容器和 O

Java类加载器的工作原理

Java类加载器的作用就是在运行时加载类.Java类加载器基于三个机制:委托.可见性和单一性.委托机制是指将加载一个类的请求交给父类加载 器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它.可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类 加载器加载的类.单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类.正确理解类加载器能够帮你解决 NoClassDefFoundError和java.lang.ClassNo

Java类加载器工作原理

Java类加载器是用来在运行时加载类(*.class文件).Java类加载器基于三个原则:委托.可见性.唯一性.委托原则把加载类的请求转发给父 类加载器,而且仅加载类当父 类加载器无法找到或者不能加载类时.可见性原则允许子类加载器查看由父类加载器加载的所有的类,但是父类加载器不能查看由子类加载器加载的类.唯一性原则只允许加载一次类文件,这基本上是通过委托原则来实现的并确保子类加载器不重新加载由父类加载器加载过的类.正确的理解类加载器原理必须解决像 NoClassDefFoundError in

java笔记--理解java类加载器以及ClassLoader类

类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制.JVM中用来完成上述功能的具体实现就是类加载器.类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象. 类的生命周期: 类从加载到虚拟机内存到被从内存中释放,经历的

深入理解:java类加载器

概念理解:Java类加载器总结 1.深入理解Java类加载器(1):Java类加载原理解析 2.深入理解Java类加载器(2):线程上下文类加载器

java类加载器深入研究

看了下面几篇关于类的加载器的文章,豁然开朗.猛击下面的地址开始看吧. Java类加载原理解析      深入探讨 Java 类加载器 分析BootstrapClassLoader/ExtClassLoader/AppClassLoader的加载路径 及"父委托机制"            

【正文】Java类加载器( CLassLoader ) 死磕 4: 神秘的双亲委托机制

[正文]Java类加载器(  CLassLoader ) 死磕4:  神秘的双亲委托机制 本小节目录 4.1. 每个类加载器都有一个parent父加载器 4.2. 类加载器之间的层次关系 4.3. 类的加载次序 4.4 双亲委托机制原理与沙箱机制 4.5. forName方法和loadClass方法的关系 4.6. 使用组合而不用继承 4.7. 各种不同的类加载途径 4.1.每个类加载器都有一个parent父加载器 每个类加载器都有一个parent父加载器,比如加载SystemConfig.cl

Java 类加载器(转)

java虚拟机中可以安装多个类加载,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap(内嵌在java虚拟机中由C++编写),ExtClassLoader,AppClassLoad    类加载器也是java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap.    java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象    或者

JVM类加载器原理与自定义类加载器

类加载器原理 JVM将class文件字节码文件加载到内存中, 并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class 对象,作为方法区类数据的访问入口. 类缓存 标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间.不过,JVM垃圾收集器可以回收这些Class过象. 类加载器数状结构 引导类加载器(bootstrap class loader) 它用来加载Java的核心库(JAVA_HOME/