设为首页 收藏本站
查看: 839|回复: 0

[经验分享] Weblogic与Java类加载器原理试验解析

[复制链接]

尚未签到

发表于 2017-2-17 11:09:53 | 显示全部楼层 |阅读模式
  通过试验,得出一个结论, 假设在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中的类大致分为三种: 
  1.系统类 
  2.扩展类 
  3.由程序员自定义的类 
  3.类装载方式,有两种 
  1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中, 
  2.显式装载, 通过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会自动创建一个具有层次结构的类装载器。
  1、a.Application Classloader负责装载应用中的所有的EJB JAR文件;
  2、b.Web Application Classloader负责装载所有的Web application 中的WAR 文件(所有得jsp文件除外);
  3、c.Jsp Classloader 负责装载Web application 中的所有的jsp 文件。
  
    Tomcat与Weblogic是相反的:对于运行在 Java EE 容器中的 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”问题时,被迫研究了上述的原理。现在问题也算是较完美的解决了,更关键的是学到了很多相关底层的知识,对于自己技术的提升是很有好处的。
  

            -东营 sparta-紫杉 原创,转载请注明出处 :)
            http://www.blogjava.net/SpartaYew/
            SpartaYew@163.com 
            
QQ:22086526

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-343406-1-1.html 上篇帖子: 安装与配置Weblogic J2EE Server并部署Jdeveloper ADF Web App 下篇帖子: Weblogic系统管理总结(7.1/8.0)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表