Tomcat 类加载机制简介

Tomcat 类加载机制简介,第1张

1)两个web服务相互隔离(java类库隔离)

2)两个web服务有些类库要共享,但是不能放多份相同的类库,会造成方法区堆积。

3)web服务器要不受web服务影响

2)JSP应用需要HotSwap功能

    所以单独一个classpath无法满足web服务器需求,所以web服务器提供了好几个classpath路径供用户存放第三方类库。不同路径的的类库具有不同访问范围和服务对象。---通常,每个目录会有一个自定义类加载器去加载放置里面的Java类库。

当tomcat启动时,会创建几种类加载器:

1 Bootstrap 引导类加载器 + Extension类加载器

    先尝试在Bootstrap(位于jre/lib/rt.jar下)和Extension(位于jre/lib/ext下)中进行类型加载。

2 System 系统类加载器(仅仅tomcat使用)

    加载tomcat启动的类 ,比如CATALINA_HOME/bin/bootstrap.jar,通常在catalina.sh中指定。位于CATALINA_HOME/bin下。

3 Common 通用类加载器 (tomcat,web程序都可以使用)

    加载tomcat使用以及应用通用的一些类 ,位于CATALINA_HOME/lib下,比如servlet-api.jar

4 webapp 应用类加载器 (仅仅web程序都可以使用)

    每个应用 在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于WEB-INF/lib下的jar文件中的class和WEB-INF/classes下的class文件。

    1 使用bootstrap引导类加载器加载(e.g. JRE中的Java基础包)

    2 使用system系统类加载器加载 (e.g. CATALINA_HOME/bin/bootstrap.jar)

    3 使用应用类加载器在WEB-INF/classes中加载

    4 使用应用类加载器在WEB-INF/lib中加载 

    5 使用common类加载器在CATALINA_HOME/lib中加载

    (e.g. CATALINA_HOME/lib/下的)

BuildPath中只支持加入jar文件,具体方法如下:

在eclips里在工程名上右键->build path->contigure bud path->java build path里有个

libraries->add external jars

add jars

add library

add class folder

这些按钮都是做什么用的

分类解释这些选项的意思:

add external jars = 增加工程外部的包

add jars = 增加工程内包

add library = 增加一个库

add class folder = 增加一个类文件夹

下面着重介绍add library中的User Libraries

添加User Library,具体做法如下:

1. 选中工程右键->build path->Add Libraries...

2. 选择User Library->next

3. 点击User Library按钮

4. 点击new按钮

5. 输入Library name(我要导入的是struts 2.1.6的jar包和jdbc的jar包,为了方便记忆,就可以用如Struts 2.1.6 Library)

6.点击ok

7.选中该user library, 然后点击add jars

8.找到对应jar包,依次确定即可。

User

Liberary加到Eclipse中,只是eclipse中生效,就是只有Eclipse知道那些引用的类放在哪里,但是如果你要web工程启动正常,

是要告诉Tomcat等容器,你的jar包是在哪里(放在lib目录下,容器就知道了)。所以就有这种情况出现,在eclipse中加用户库,只是为了调

试,不加入用户库,eclipse找不到import的类,就会出现红色的X号,不把用户库中的JAR包放到lib下,容量找不到引入的类,就会报错。

add jar 和add external jars 与add library 中User Libraries的区别是:

通过“add jar” 和“add external jars”添加的jar包作为程序的一部分被打包到最终的程序中。通过“User Libraries”添加的jar包不是。

关于Jar包 build path的作用:

jar包不能在随意的地方。 不管是Java Application 还是 Java Web Application 。

Java虚拟机是根据Java ClassLoader(类加载器)决定如何,到那里去加载Class :

我们之所以把jar包放在classPath下,是因为存在ClassPath ClassLoader

我们之所以可以不在ClassPath指定一些Jar包,但在Java程序中也能使用。

那是因为有ClassPath ClassLoader的父类加载器负责加载。如jrd目录下jre\lib\*.jar

我们之所以把Jar包放入webroot下的lib文件夹,并且可以在我们的程序中使用,那是容器实现了自己的ClassLoader。(Web中间件服务器类加载的机制和sun公司提供的3个默认加载器不同。)

所以说能不能加载Jar,加载哪里的Jar,是根据ClassLoader决定的

这个问题经常出现在编写框架代码 , 需要动态加载很多类和资源的时候 . 通常当你需要动态加载资源的时候 , 你至少有三个 ClassLoader 可以选择 :

²系统类加载器或叫作应用类加载器 (system classloader or application classloader)

²当前类加载器

²当前线程类加载器

上面的问题指的是最后一种类加载器 . 哪种类加载器是正确的选择呢 ?

第一种选择可以很容易地排除 : 系统类加载器 (system classloader). 这个类加载器处理 -classpath 下的类加载工作 , 可以通过 ClassLoader.getSystemClassLoader() 方法调用 . ClassLoader 下所有的 getSystemXXX() 的静态方法都是通过这个方法定义的 . 在你的代码中 , 你应该尽量少地调用这个方法 , 以其它的类加载器作为代理 . 否则你的代码将只能工作在简单的命令行应用中 , 这个时候系统类加载器 (system classloader) 是 JVM 最后创建的类加载器 . 一但你把代码移到 EJB, Web 应用或 Java Web Start 应用中 , 一定会出问题 .

所以我们来看第二种选择 : 当前上下文环境下的类加载器 . 根据定义 , 当前类加载器就是你当前方法所属的类的加载器 . 在运行时类之间动态联编 , 及调用 Class.forName,() Class.getResource() 等类似方法时 , 这个类加载器会被隐含地使用 . It is also used by syntactic constructs like X.class class literals.

线程上下文类型加载器是在Java 2平台上被引入的. 每一个线程都有一个类加载器与之对应(除非这个线程是被本地代码创建的). 这个类加载器是通过Thread.setContextClassLoaser()方法设置的. 如果你不在线程构造后调用这个方法, 这个线程将从它的父线程中继承相应的上下文类加载器. 如果在整个应用中你不做任何特殊设置, 所有的线程将都以系统类加载器(system classloader)作为自己的线程上下文类加载器. 自从Web和J2EE应用服务器使用成熟的类加载器机制来实现诸如JNDI, 线程池, 组件热部署等功能以来, 这种在整个应用中不做任何线程类加载器设置的情况就很少了.

为什么线程上下文类加载器存在于如此重要的位置呢? 这个概念在J2SE中的引入并不引人注目. 很多开发人员对这一概念迷惑的原因是Sun公司在这方面缺乏适当的指引和文档.

事实上, 上下文类加载器提供了类加载机制的后门, 这一点也在J2SE中被引入了. 通常, 在JVM中的所有类加载器被组织成了有继承层次的结构, 每一个类加载器(除了引导JVM的原始类加载器)都有一个父加载器. 每当被请示加载类时, 类加载器都会首先请求其父类加载器, 只有当父类加载器不能加载时, 才会自己进行类加载.

有时候这种类加载的顺序安排不能正常工作, (此处的意思是:正常情况下都是从子类加载器到根类加载器请求,万一有根类里需要加载子类时,这种顺序就不能满足要求,就要有一条反向的通道,即得到子类加载器,这样就用到了thread context classloader,因为通过thread.getcontextclassloader()可以得到子类加载器).通常当必须动态加载应用程序开发人员提供的资源的时候. 以JNDI为例: 它的内容(从J2SE1.3开始)就在rt.jar中的引导类中实现了, 但是这些JNDI核心类需要动态加载由独立厂商实现并部署在应用程序的classpath下的JNDI提供者. 这种情况就要求一个父classloader(本例, 就是引导类加载器)去加载对于它其中一个子classloader(本例, 系统类加载器)可见的类. 这时通常的类加载代理机制不能实现这个要求. 解决的办法(workaround)就是, 让JNDI核心类使用当前线程上下文的类加载器, 这样, 就基本的类加载代理机制的相反方向建立了一条有效的途径.

另外, 上面一段可能让你想起一些其它的事情: XML解析Java API(JAXP). 是的, 当JAXP只是J2SE的扩展进, 它很自然地用当前类加载器来引导解析器的实现. 而当JAXP被加入到J2SE1.4的核心类库中时, 它的类加载也就改成了用当前线程类加载器, 与JNDI的情况完全类似(也使很多程序员很迷惑). 明白为什么我说来自Sun的指导很缺乏了吧?

在以上的介绍之后, 我们来看关键问题: 这两种选择(当前类加载器和当前线程类加载器)都不是在所有环境下都适用. 有些人认为当前线程类加载器应该成为新的标准策略. 但是, 如果这样, 当多个线程通过共享数据进行交互的时, 将会呈现出一幅极其复杂的类加载的画面, 除非它们全部使用了同一个上下文的类加载器. 进一步说, 在某些遗留下来的解决方案中, 委派到当前类加载器的方法已经是标准. 比如对Class.forName(String)的直接调用(这也是我为什么推荐尽量避免对这个方法进行调用的原因). 即使你努力去只调用上下文相关的类加载器, 仍然会有一些代码会不由你控制. 这种不受控制的类加载委派机制是混入是很危险的.

更严重的问题, 某些应用服务器把环境上下文及当前类加载器设置到不同的类加载器实例上, 而这些类加载器有相同的类路径但却没有委派机制中的父子关系. 想想这为什么十分可怕. 要知道类加载器定义并加载的类实例会带有一个JVM内部的ID号. 如果当前类加载器加载一个类X的实例, 这个实例调用JNDI查找类Y的实例, 些时的上下文的类加载器也可以定义了加载类Y实例. 这个类Y的定义就与当前类加载器看到的类Y的定义不同. 如果进行强制类型转换, 则产生异常.

这种混乱的情况还将在Java中存在一段时间. 对于那些需要动态加载资源的J2SE的API, 我们来猜想它们的类加策略. 例如:

Ø JNDI 使用线程上下文类加载器

Ø Class.getResource() 和Class.forName()使用当前类加载器

Ø JAXP(J2SE 1.4 及之后)使用线程上下文类加载器

Ø java.util.ResourceBundle 使用调用者的当前类加载器

Ø URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only

Ø Java 序列化API默认使用调用者当前的类加载器

这些类及资源的加载策略问题, 肯定是J2SE领域中文档最及说明最缺乏的部分了.


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/638344.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-07-19
下一篇2023-07-19

发表评论

登录后才能评论

评论列表(0条)

    保存