ConcurrentHashMap崩溃使用JDK 8编译但是以JRE 7为目标的应用程序

时间:2015-10-05 17:06:56

标签: java iteration concurrenthashmap

我今天遇到了一个非常意想不到的错误,虽然我能够找到一种方法来解决整个问题,但我不确定我是否完全明白为什么会这样做。

我正在使用的代码最初是用JDK 7环境编写的,当然是针对JRE 7.在代码中我使用的是ConcurrentHashMap,需要迭代映射中的键。为此,我使用了map.keySet(),根据JavaDocs,它应返回Set<K>。这很好,直到我们的构建环境切换到JDK8。

当我们转移到JDK8时,我确保在调用javac时调用1.7的目标/源。当代码在想要遍历地图的键时开始失败时,我感到非常惊讶。没有抛出任何错误,没有异常,线程只是停止了。在做了一些研究后,我发现Java8的ConcurrentHashMap .keySet()方法的实现返回KeySetView<K,V>

我通过使用map.keySet()切换到使用Enumeration<K>获取map.keys()来解决问题。

现在我对这个问题的猜测是,虽然项目是针对Java7编译的,因为使用了JDK8,但是包含了Java8库,但是为什么它在遇到不匹配时没有抛出错误或异常?

这里提到的是一段代码片段:

class MapProcessing
{
     private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();

     public MapProcessing()
     {
           map.put("First",new Object());
           map.put("Second",new Object());
           map.put("Third",new Object());
     } 


     public void processing()
     {
          // when calling this type of loop causes a freeze on our system.
          for(String key : map.keySet())
          {
              System.out.println(key);
          }
      }

     public void working()
     {
         // This is what I had to do to fix the problem.
         Enumeration<String> keys = map.keys();
         while(keys.hasMoreElements())
         {
              String key = keys.nextElement();
              System.out.println(key);
         }
     }
} 

我们正在使用Oracle JDK 8 build 40在Windows 2012服务器上的javac中使用1.7和1.7的目标进行编译。

代码正在使用在Windows 2012服务器上运行的Oracle JVM 7 build 25运行。

2 个答案:

答案 0 :(得分:12)

如果我用Java 8和javac -source 1.7 -target 1.8编译你的代码,然后用Java 7运行它我得到一个

Exception in thread "main" java.lang.NoSuchMethodError:
  java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
    at stackoverflowt.Test.processing(Test.java:20)
    at stackoverflowt.Test.main(Test.java:27)   

这是因为字节代码看起来像

public void processing();
    Code:
       0: aload_0       
       1: getfield      #4                  // Field map:Ljava/util/concurrent/ConcurrentHashMap;
       4: invokevirtual #10                 // Method java/util/concurrent/ConcurrentHashMap.keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
       7: invokevirtual #11                 // Method java/util/concurrent/ConcurrentHashMap$KeySetView.iterator:()Ljava/util/Iterator;
      10: astore_1      

并显式引用了Java 7中不存在的ConcurrentHashMap $ KeySetView。我在Mac上使用Java 1.7.0_79和1.8.0_45

如果您将代码更改为(仅使用地图界面):

private Map<String, Object> map = new ConcurrentHashMap<String, Object>();

然后它适合我。 Bytecode然后看起来像

public void processing();
    Code:
       0: aload_0       
       1: getfield      #4                  // Field map:Ljava/util/Map;
       4: invokeinterface #10,  1           // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
       9: invokeinterface #11,  1           // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
      14: astore_1      

答案 1 :(得分:2)

每当使用针对旧版本的-source参数使用较新的JDK构建项目时,您将收到此编译器警告:

warning: [options] bootstrap class path not set in conjunction with -source 1.7

This blog entry谈论它意味着什么。

基本上,你得到这个警告是因为Java使用较旧的语言规则编译它,但是针对较新的类库...并且由于Oracle移动了一些内部类,因此Java 8版本存在一些兼容性问题。

修复是在编译时使用-bootclasspath参数将其指向旧版本的rt.jar