ConcurrentHashMap crashing application compiled wi

2019-02-09 07:36发布

问题:

I ran into a very unexpected error today and while I was able to find a way to fix the problem as a whole I'm not sure I completely understand why it did what it did.

The code I'm working with was originally written with a JDK 7 environment of course targeting JRE 7. In the code I was using a ConcurrentHashMap and needed to iterate over the keys in the map. For this I was using the map.keySet() which according to the JavaDocs should return a Set<K>. This worked fine until our build environment switched to JDK8.

When we moved to JDK8 I ensured that I was calling a target/source for 1.7 when calling the javac. So I was pretty surprised when the code started failing right when it wanted to iterate through the keys of the map. No error was thrown, no exception, the thread just simply stopped. After doing some research I found that Java8's implementation for ConcurrentHashMap the .keySet() method returns a KeySetView<K,V>.

I fixed the problem by switching from using the map.keySet() to getting an Enumeration<K> using map.keys().

Now my guess as to the problem is that although the project was compiled targeting Java7 since the JDK8 was used the Java8 libraries were included, but why didn't it thrown an error or an exception when it hit the mismatch?

As asked here is a code snippet:

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);
         }
     }
} 

We are compiling using Oracle JDK 8 build 40 using a target for 1.7 and source 1.7 in the javac on a Windows 2012 server.

The code is running using Oracle JVM 7 build 25 running on Windows 2012 server.

回答1:

If i compile your code with Java 8 and javac -source 1.7 -target 1.8 and then run it with Java 7 i get an

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)   

This is because the the byte code looks like

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      

and referring explicitly to ConcurrentHashMap$KeySetView which is not present in Java 7. I am on Mac with Java 1.7.0_79 and 1.8.0_45

If you change the code to (only use the Map Interface):

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

then it work's for me. Bytecode then looks like

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      


回答2:

Whenever you build a project using a newer JDK using the -source argument targeting an older version, you'll get this compiler warning:

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

This blog entry talks about what it means.

Basically, you get this warning because Java is compiling it using older language rules but against the newer class library... and there are some compatibility issues with the Java 8 versions as Oracle moved some of the internal classes around.

The fix is to use the -bootclasspath argument to point it at the rt.jar from the older version while compiling.