Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

这问题蛮有意思,题目本身会涉及到jvm相关的两个话题。整篇内容是分析贴,没有博文视角。

  1. classloader的种类和加载顺序
  2. 当出现相同class jvm是如何处理的

这里我们先不说结论(网上很多)结论留在文章末尾,我在本地 maven 工程下进行尝试的时候发现一个有趣的现象,

像这样的写法大家猜猜会如何加载呢?如果你尝试的话就会发现一个问题

只会加载 3.4 这个版本,也就是maven中只加载最后一个配置的依赖(当grouopId和artifactId相同的时候,版本不同)。

maven的仲裁机制: http://yangbolin.cn/2014/06/18/maven-default-arbitration/

这样的话我们根本无法重现一个项目加载两个相同jar(不同版本),可以把 IDE 运行时的命令(也就是加载了哪些jar到classpat)同时添加两个版本,那么命令大概如下:

/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/bin/java -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:64728,suspend=y,server=n -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/jre/lib/charsets.jar:/...(此处省去很多jar太长了).../commons-lang3-3.5.jar:/Users/biezhi/Library/Application Support/JetBrains/Toolbox/apps/IDEA-U/ch-0/172.4343.14/IntelliJ IDEA.app/Contents/lib/idea_rt.jar" com.kongzhong.app.gateway.LangTest

上面这段是 JVM 启动时加载类的命令,简单介绍下结构:

java -Dfile.encoding=UTF-8 -classpath "a.jar:b.jar" mainClass

大概是这样的结构,IDE里还有一些其他的像代理等参数就不写了。 指定了类加载时候的 classpath,上面 IDE 中执行仔细去看的话会发现只加载了一个 commons-lang 而且是根据 maven 的仲裁机制选择的,我们只需要将命令修改一下,把 2 个不同版本的都加进去,来试试。

java -Dfile.encoding=UTF-8 -classpath "/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/jre/lib/ext/dnsns.jar:(省去了,你懂的):/Users/biezhi/workspace/work/kongzhong/app-gateway/target/classes:/usr/local/apache-maven-3.3.9/repo/org/apache/commons/commons-lang3/3.4/commons-lang3-3.4.jar:/usr/local/apache-maven-3.3.9/repo/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar" com.kongzhong.app.gateway.LangTest

大概就是这样,我的 LangTest 类是这样的:

package com.kongzhong.app.gateway;

import org.apache.commons.lang3.StringUtils;

/**
 * @author biezhi
 * @date 2017/12/20
 */
public class LangTest {

    public static void main(String[] args) {
        System.out.println("lang test");
        System.out.println(StringUtils.isNotBlank("Hello"));
    }

}

如果你执行的话会发现一个问题,输出永远都是

lang test
Hello

那么有人就开始疑问了,难道加载了2个jar不会导致冲突吗? 其实这时候反应出了一个问题,什么情况下会导致不同Jar包的冲突? 下面是三个必要条件:

  1. 同一个类 M 出现在了多个依赖的Jar包中(假设是2个相同jar A 和 B)
  2. Jar包 A 和 B 中的该类 M 有差异,无论是方法签名不同也好,成员变量不同也好,只要可以造成实际加载的类的行为和期望不一致都行。如果说Jar包 A 和 B 中的该类完全一样,那么类加载器无论先加载哪个Jar包,得到的都是同样版本的类 M ,不会有任何影响,也就不会出现Jar包冲突带来的诡异问题。
  3. 加载的类 M 不是所期望的版本,即加载了错误的Jar包

上面所说的 A 和 B 分别是我们例子中的 commons-lang-3.4 和 3.5 M 则是 StringUtils 这个类。所以说假设两个版本中 StringUtils.isNotBlank 的实现不同则会导致 NoSuchMethodError 如果是由于 maven 仲裁机制导致可能会产生 ClassNotFoundException

导致冲突的本质

  1. 应用程序依赖的同一个Jar包出现了多个不同版本,并选择了错误的版本而导致JVM加载不到需要的类或加载了错误版本的类
  2. 同样的类(类的全限定名完全一样)出现在多个不同的依赖Jar包中,即该类有多个版本,并由于Jar包加载的先后顺序导致JVM加载了错误版本的类。

这两种情况所导致的结果其实是一样的,都会使应用程序加载不到正确的类,那其行为自然会跟预期不一致了。


还有一个问题,类加载顺序到底是怎么样的?

我不分析了。。。直接贴文章吧

  1. http://blog.csdn.net/briblue/article/details/54973413
  2. http://www.jianshu.com/p/808a36134da5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.