java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet() 异常分析

java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet() 异常发生的原因其实很简单,就是编译和运行的版本的JDK不一致造成的。JDK8以下版本和和JDK8及以上版本。
Java高编译低运行错误(ConcurrentHashMap.keySet) 这篇文章说的很清楚,可以参考。以下我再从字节码的角度说明一下,更容易理解一些。

1. ConcurrentHashMap API 变更

  • JDK8 以下版本
    keySet 返回值是Set 对象。

    1
    2
    3
    4
    public Set<K> keySet() {
    Set<K> ks = keySet;
    return (ks != null) ? ks : (keySet = new KeySet());
    }
  • JDK8及以上版本
    keySet 返回值是KeySetView 对象。

1
2
3
4
public KeySetView<K,V> keySet() {
KeySetView<K,V> ks;
return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null));
}
  • 接口
    高低版本的接口是一样的:
    1
    Set<K> keySet();

2、测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Test {
public static void test1() {
// 对象调用
ConcurrentHashMap map = new ConcurrentHashMap();
map.keySet();
}

public static void test2() {
//接口调用
ConcurrentMap map = new ConcurrentHashMap();
map.keySet();
}
}

3、字节码

  • JDK8 以下版本
    实例调用:
    1
    INVOKEVIRTUAL java/util/concurrent/ConcurrentHashMap.keySet()Ljava/util/Set;

接口调用:

1
INVOKEINTERFACE java/util/concurrent/ConcurrentMap.keySet()Ljava/util/Set;

  • JDK8及以上版本
    实例调用:
    1
    INVOKEVIRTUAL java/util/concurrent/ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;

接口调用:

1
INVOKEINTERFACE java/util/concurrent/ConcurrentMap.keySet()Ljava/util/Set;

在高版本上编译生成的字节码为调用的方法为,java/util/concurrent/ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; 方法名称为keySet,返回值为KeySetView,而这样的方法在低版本的JDK上是不存在的,同理低版本的编译在高版本上调用也是不存在的。
高低版本的JDK接口是一致的,所以调用接口是没有问题的。

4、继承的处理

在开发Apptalking Agent的时候,Agent 是在JDK6 编译的,为了兼容低版本的 JDK,不可能升高编译的JDK。并且开发的时候是继承了ConcurrentHashMap,在高版本的JDK上运行同样出现了上述的问题。解决的方式修改POM 中的 bootclasspath

1
2
3
4
5
6
7
8
9
10
11
12
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArguments>
<verbose />
<bootclasspath>D:\Software\Java\Jdk\6\Jar\lib\rt.jar;D:\Software\Java\Jdk\6\Jar\lib\jsse.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>