2015-10-05 6 views
8

Ich stieß heute auf einen sehr unerwarteten Fehler und während ich in der Lage war, das Problem als Ganzes zu beheben, bin ich mir nicht ganz sicher, warum es was tat es tat es.ConcurrentHashMap Absturz-Anwendung kompiliert mit JDK 8 aber Targeting JRE 7

Der Code, mit dem ich arbeite, wurde ursprünglich mit einer JDK 7-Umgebung geschrieben, die natürlich auf JRE 7 abzielte. Im Code verwendete ich eine ConcurrentHashMap und musste über die Schlüssel in der Map iterieren. Dazu habe ich die map.keySet() verwendet, die laut JavaDocs eine Set<K> zurückgeben soll. Dies funktionierte gut, bis unsere Build-Umgebung zu JDK8 wechselte.

Als wir zu JDK8 wechselten, stellte ich sicher, dass ich beim Aufruf des Javacs ein Ziel/eine Quelle für 1.7 anrief. So war ich ziemlich überrascht, als der Code zu versagen begann, als er die Schlüssel der Karte durchlaufen wollte. Es wurde kein Fehler ausgelöst, keine Ausnahme, der Thread wurde einfach gestoppt. Nach einigen Recherchen fand ich heraus, dass Java8-Implementierung für ConcurrentHashMap die .keySet()-Methode eine KeySetView<K,V> zurückgibt.

Ich habe das Problem behoben, indem ich von map.keySet() auf Enumeration<K> mit map.keys() umgestellt habe.

Jetzt ist meine Vermutung, das Problem ist, dass, obwohl das Projekt kompiliert Targeting Java7 seit dem JDK8 verwendet wurde die Java8-Bibliotheken enthalten waren, aber warum hat es nicht einen Fehler oder eine Ausnahme geworfen, wenn es das Mismatch getroffen?

Wie gefragt ist hier ein Code-Schnipsel:

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

Wir kompilieren verwenden Oracle JDK 8 build 40 ein Ziel für 1,7 und 1,7 Quelle im javac auf einem Windows 2012 Server.

Der Code wird mit Oracle JVM 7 Build 25 unter Windows 2012-Server ausgeführt.

+0

Haben Sie versucht, es nach dem Anschließen JDK 8 zu reinigen? –

+0

@suresh die Build-Umgebung ist ein Jenkins. Als wir zu JDK8 wechselten, löschten wir den Arbeitsbereich und führten dann einen neuen Build durch. Dies würde bedeuten, dass der Code frisch aus SVN ausgecheckt und dann erstellt wurde. Ich bin mir nicht sicher, was Sie meinen, indem Sie es anders als den Standardaufruf zu einem "sauberen" Ziel in der Ameise reinigen, die geschieht. – JRSofty

+0

können Sie ein Code-Snippet und die genaue JVM-Version und Hersteller + OS? KeySetView implementiert Set , so dass dies zumindest kein Problem sein sollte – salyh

Antwort

12

Wenn ich Ihren Code kompilieren mit Java 8 und javac -source 1,7 -target 1.8 und führen Sie es dann mit Java 7 ich eine

 
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) 

Dies liegt daran, das die Byte-Code wie

sieht
 
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  

und explizit auf ConcurrentHashMap $ KeySetView beziehen, die in Java 7 nicht vorhanden ist. Ich bin auf Mac mit Java 1.7.0_79 und 1.8.0_45

Wenn Sie den Code (nur verwenden, um die Karte Interface) ändern:

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

es dann die für mich arbeiten. Bytecode sieht dann aus wie

 
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

Interessant. Ich hatte nicht über die generische Map-Schnittstelle nachgedacht. Leider habe ich nie einen 'NoSuchMethodError' gesehen. Das hätte ich zumindest erwartet. Ich muss morgen wieder in den Kabeljau schauen, um zu sehen, ob die Fehlerbehandlung tatsächlich alles korrekt abfängt. Vielen Dank. – JRSofty

+0

Ich habe heute Morgen meinen Code überprüft und der Aufruf, der alles absetzt, ist in einen try catch gehüllt, aber nur abfangen von Ausnahmen und 'NoSuchMethodError' ist' Throwable', aber keine 'Exception'. Dies gibt mir ein besseres Verständnis, warum ich keinen Fehler sah, als das Programm brach. – JRSofty

+0

@JRSofty Wenn es ein Problem ist, dass Java es nicht wiederherstellen kann, wird es ein ['Error'] (https://docs.oracle.com/javase/8/docs/api/java/lang/Error .html), die eine Unterklasse von 'Throwable' ist, aber nicht' Exception'. – Powerlord

2

Jedes Mal, wenn Sie ein Projekt erstellen, eine neuere JDK mit dem -source Argumente eine ältere Version Targeting werden Sie diese Compiler-Warnung erhalten:

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

This blog entry Gespräche über das, was es bedeutet.

Im Grunde erhalten Sie diese Warnung, weil Java es mit älteren Sprachregeln, aber gegen die neuere Klassenbibliothek kompiliert ... und es gibt einige Kompatibilitätsprobleme mit den Java 8 Versionen, da Oracle einige der internen Klassen verschoben hat.

Das Problem besteht darin, das -bootclasspath-Argument zu verwenden, um es während der Kompilierung auf die aus der älteren Version zu verweisen.

+0

Ok, das Hinzufügen zum richtigen rt.jar mit der Option "-bootclasspath" sollte also wie erwartet funktionieren. Obwohl ich denke, meine Lösung ist wahrscheinlich besser, da es bedeutet, wenn wir die Anwendung auf einem System mit JRE 8 bereitstellen, dann muss ich mich nicht darum kümmern, dass es kaputt geht. Was mich interessiert, ist, warum habe ich keine Ausnahme oder einen Fehler bekommen, als ich das Programm ausgeführt habe? Bei mir hat der Thread einfach aufgehört und es wurde keine Ausnahme geworfen. – JRSofty

+0

Nochmals vielen Dank für die Informationen über die Build-Umgebung. Ich werde das so schnell wie möglich beheben. Wir haben der 'javac' ant-Aufgabe niemals die' bootclasspath'-Option hinzugefügt. Ich werde das sofort machen. – JRSofty