2015-10-05 14 views
8

Ho avuto un errore molto inaspettato oggi e mentre sono riuscito a trovare un modo per risolvere il problema nel suo complesso non sono sicuro di capire completamente perché ha fatto cosa lo ha fatto.Applicazione Crash ConcurrentHashMap compilata con JDK 8 ma con targeting JRE 7

Il codice con cui sto lavorando è stato originariamente scritto con un JDK 7 ambiente naturalmente mira JRE 7. Nel codice Io sto usando un ConcurrentHashMap e necessari per scandire le chiavi nella mappa. Per questo stavo usando il map.keySet() che secondo JavaDocs dovrebbe restituire un Set<K>. Questo ha funzionato fino a quando il nostro ambiente di costruzione non è passato a JDK8.

Quando siamo passati a JDK8 mi sono assicurato che stavo chiamando un target/source per 1.7 quando si chiama il javac. Quindi sono rimasto piuttosto sorpreso quando il codice ha iniziato a non funzionare correttamente quando voleva scorrere le chiavi della mappa. Nessun errore è stato lanciato, nessuna eccezione, il thread semplicemente si è fermato. Dopo aver effettuato alcune ricerche, ho scoperto che l'implementazione di Java8 per ConcurrentHashMap il metodo .keySet() restituisce un valore KeySetView<K,V>.

Ho risolto il problema passando dall'utilizzo di map.keySet() a un Enumeration<K> utilizzando map.keys().

Ora la mia ipotesi sul problema è che sebbene il progetto sia stato compilato per il targeting Java7 dal momento che il JDK8 è stato utilizzato, le librerie Java8 sono state incluse, ma perché non ha generato un errore o un'eccezione quando ha colpito la mancata corrispondenza?

come chiesto: ecco un frammento di codice:

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

Stiamo compilando utilizzando Oracle JDK 8 Build 40 con un obiettivo per 1,7 e 1,7 fonte nel javac su un server Windows 2012.

Il codice è in esecuzione utilizzando Oracle JVM 7 build 25 in esecuzione sul server Windows 2012.

+0

Hai provato a pulirlo dopo aver collegato JDK 8? –

+0

@suresh l'ambiente di costruzione è un Jenkins. Quando ci siamo spostati su JDK8 abbiamo eliminato lo spazio di lavoro e poi eseguito una nuova build. Ciò significherebbe che il codice è stato appena estratto da SVN e quindi creato. Non sono sicuro di cosa intendi per pulirlo a parte la chiamata standard a un target "pulito" nella formica, cosa che succede. – JRSofty

+0

puoi pubblicare uno snippet di codice e la versione esatta di JVM e il sistema operativo + OS? Strumenti KeySetView Impostare in modo che almeno questo non dovrebbe essere un problema reale – salyh

risposta

12

Se compilo il codice con Java 8 e javac -source 1.7 -target 1.8 e quindi eseguirlo con Java 7 ottengo un

 
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) 

Questo perché il codice byte assomiglia

 
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  

e si riferiscono esplicitamente a ConcurrentHashMap $ KeySetView che non è presente in Java 7. Sono su Mac con Java 1.7.0_79 e 1.8.0_45

Se si modifica il codice per (usare solo la mappa Interface):

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

allora il lavoro di per me. Bytecode appare come

 
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

Interessante. Non avevo pensato di usare l'interfaccia generica della mappa. Purtroppo, non ho mai visto un 'NoSuchMethodError'. Mi sarei aspettato almeno questo. Domani dovrò controllare nuovamente il merluzzo per vedere se la gestione degli errori sta effettivamente catturando tutto correttamente. Grazie. – JRSofty

+0

Stamattina ho controllato il mio codice e sicuramente la chiamata che spazza via tutto è racchiusa in un tentativo di cattura ma rileva solo eccezioni e 'NoSuchMethodError' è' Throwable' ma non un 'Exception'. Questo mi dà una migliore comprensione del motivo per cui non ho visto un errore quando il programma si è rotto. – JRSofty

+0

@JRSofty Se si tratta di un problema che Java non può recuperare da esso, sarà un ['Errore'] (https://docs.oracle.com/javase/8/docs/api/java/lang/Error .html), che è una sottoclasse di 'Throwable' ma non' Exception'. – Powerlord

2

Ogni volta che si genera un progetto con una più recente JDK utilizzando l'argomento -source mira una versione precedente, si otterrà questo avviso del compilatore:

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

This blog entry parla di ciò che significa.

Fondamentalmente, si ottiene questo avviso perché Java lo sta compilando utilizzando le regole della lingua precedente ma contro la nuova libreria di classi ... e ci sono alcuni problemi di compatibilità con le versioni di Java 8 come Oracle spostato alcune delle classi interne intorno.

La correzione consiste nell'utilizzare l'argomento -bootclasspath per puntarlo sullo rt.jar dalla versione precedente durante la compilazione.

+0

Ok, quindi aggiungere il puntamento al file rt.jar corretto usando l'opzione '-bootclasspath' dovrebbe funzionare come previsto. Anche se suppongo che la mia correzione sia probabilmente migliore perché significa che quando distribuiamo l'applicazione su un sistema su cui gira JRE 8, non devo preoccuparmi che si rompa. Ciò di cui sono curioso è perché non ho ricevuto un'eccezione o un errore durante l'esecuzione del programma? Per me il thread si è fermato e non è stata lanciata alcuna eccezione. – JRSofty

+0

Grazie ancora per le informazioni sull'ambiente di costruzione. Lo aggiusterò il prima possibile. Non abbiamo mai aggiunto l'opzione 'bootclasspath' al task' javac' ant. Lo farò immediatamente. – JRSofty