从一次Java类加载冲突说起
之前在网易做一个项目时,发现了如下java包是重复引入有冲突
分析
学会了用Arthas 工具(Arthas 是Alibaba开源的Java诊断工具)分析加载的类sc -df org.apache.commons.codec.* ,还有dependency.txt,目前看:
1.有直接引用的,用直接引用的
2.都是间接的,用最新的
dependency.txt中如下httpcore确实被两个引用到了
[[1;34mINFO[m] | +- org.apache.httpcomponents:httpcore:jar:4.4.9:compile
[[1;34mINFO[m] | \- commons-codec:commons-codec:jar:1.11:compile
[[1;34mINFO[m] +- commons-io:commons-io:jar:1.3.2:compile
[[1;34mINFO[m] +- com.netease.cloud:nos-sdk-java:jar:1.2.6:compile
[[1;34mINFO[m] | +- org.apache.httpcomponents:httpcore:pom:4.1:compile
那么Java的类加载机制到底是怎么样的?
如上的例子,说明引入同名的jar包的情况是存在的,可能有人奇怪为什么会这样,这正是Java类加载机制需要回答的问题。如下图简单明了说明了类加载机制过程和优先级 我们可以用一段代码验证一下
// First, check if the class has already been loaded 先检查有没有加载过,加载过就不用在加载了,简单
Class c = findLoadedClass(name);
if (c == null) { --如果还没加载
long t0 = System.nanoTime();
try {
if (parent != null) { c = parent.loadClass(name, false); }
else { c = findBootstrapClassOrNull(name); }
//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
} catch (ClassNotFoundException e) {
...
}
return c;
深入理解Java类加载机制
如果把JVM比喻成一个俱乐部,那么这个俱乐部的会员都是谁请来的呢?类似于传销模式那样
-
首先,特点就是灵活。没有死板的规定,从一个文件中读取,而是多种途径,由应用自己决定。体现了“去中心化”的邀请 -
新会员加入时,引荐的类都必须先转给父辈,父类来加载,加载不成功才由本类加载;父辈的老朋友,必须由父辈邀请,而且邀请前看看他是否已经在俱乐部了,这样的好处是
-
这个机制是保证“增量优化” -
由不同ClassLoader对象加载的同名类属于不同类
JVM看来是个松散俱乐部,你引荐的和他引荐的可能是相同人或者同名人,不要紧,你的圈子里,只认由加载你的加载器加载的那个类
那么问题来了,为什么Tomcat不遵守这个规定?
双亲委派机制不是java的强制规范,但是是一种推荐标准
好处如下所说,一个强规则的等级社会,确保基础的类,比如java.lang.Object不会出现紊乱
那么tomcat为什么要故意绕开这种机制,因为具体情况不同:
-
一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。 如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的累加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份 -
部署在同一个web容器中相同的类库相同的版本可以共享。 -
web容器也有自己依赖的类库,不能于应用程序的类库混淆 -
jsp热修改问题
我们想我们要怎么实现jsp文件的热修改(楼主起的名字),jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
Arthas实战分析
首先,Arthas非常好用(但是注意尽量避免用到生产环境,踩过坑,对生产环境性能和稳定性有影响,毕竟其原理是替换生产在运行的class) 使用过后分析,现在jvm中实际运行的类,不再是jar包中了,很多都是动态代理的 为什么?公共的功能统一加!!! 比如
-
日志多打一些 -
执行时间统计出来 -
统一走某个session验证
实战中有很多例子,值得思考的是,就算动态代理更慢,但是哨兵和SpringBoot的都选了动态代理;而不选择快但是麻烦的静态代理(编译时就确定)
|