2014-04-25 21 views
8

Program, który opracowałem, czasami powoduje awarię maszyny JVM z powodu tego błędu: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8029516. Niestety błąd nie został rozwiązany przez Oracle, a raport o błędzie mówi, że nie ma znanych rozwiązań.Obejście problemu z błędem Javy, który powoduje zrzut awaryjny

Próbowałem zmodyfikować przykładowy kod z raportu o błędzie, wywołując zamiast niego atrybut .register (sWatchService, eventKinds) w wątku KeyWatcher, dodając wszystkie oczekujące żądania rejestrów do listy, którą przeglądam w wątku KeyWatcher, ale wciąż się psuje. Zgaduję, że to miało taki sam efekt, jak synchronizacja na sWatchService (jak próbował proces zgłaszający błąd).

Czy możesz wymyślić jakiś sposób obejścia tego?

+0

Nieparzysty. Mi to pasuje. Właśnie sprawdziłem dwa razy. – Yrlec

+0

Link zadziałał i nie zadziałał u mnie. Prawdopodobnie problem z Oracle. Nie widząc kodu, trudno jest odpowiedzieć, ale zmienić swój kod tak, aby miał ** jeden i tylko jeden wątek ** odpowiedzialny za klasy WatchService i WatchKey. Inne wątki będą używać tych klas lub usług za pośrednictwem klasy obserwatora. –

+0

Jest to problem z pamięcią zwalniającą pamięć rodzimą. Podejrzewam, że jest to biblioteka Windows, która implementuje malloc/free, która jest winna. Sprawdziłbym, czy masz najnowszą bibliotekę DLL. –

Odpowiedz

3

Udało mi się utworzyć obejście, chociaż jest ono nieco brzydkie.

Błąd jest w metodzie JDK WindowsWatchKey.invalidate(), która zwalnia macierzysty bufor, podczas gdy kolejne wywołania mogą nadal uzyskiwać do niego dostęp. This one-liner rozwiązuje problem, opóźniając czyszczenie bufora aż do GC.

Tutaj jest skompilowany patch do JDK.W celu zastosowania go dodać następujące Java wiersza polecenia flag
-Xbootclasspath/p:jdk-8029516-patch.jar

Jeśli łatanie JDK nie jest rozwiązaniem w przypadku, nadal istnieje obejście na poziomie aplikacji. Opiera się na wiedzy na temat wewnętrznej implementacji Windows WatchService.

public class JDK_8029516 { 
    private static final Field bufferField = getField("sun.nio.fs.WindowsWatchService$WindowsWatchKey", "buffer"); 
    private static final Field cleanerField = getField("sun.nio.fs.NativeBuffer", "cleaner"); 
    private static final Cleaner dummyCleaner = Cleaner.create(Thread.class, new Thread()); 

    private static Field getField(String className, String fieldName) { 
     try { 
      Field f = Class.forName(className).getDeclaredField(fieldName); 
      f.setAccessible(true); 
      return f; 
     } catch (Exception e) { 
      throw new IllegalStateException(e); 
     } 
    } 

    public static void patch(WatchKey key) { 
     try { 
      cleanerField.set(bufferField.get(key), dummyCleaner); 
     } catch (IllegalAccessException e) { 
      throw new IllegalStateException(e); 
     } 
    } 
} 

połączeń JDK_8029516.patch(watchKey) zaraz po klucz jest zarejestrowany i będzie zapobiegać watchKey.cancel() od zwolnieniu natywną bufor przedwcześnie.

+0

Awesome! Działało idealnie. Jestem pod wrażeniem i wdzięczny! – Yrlec

+0

Nice! @apangin czy rozważałeś opublikowanie proponowanej poprawki na http://mail.openjdk.java.net/mailman/listinfo/core-libs-dev liście mailingowej? – anttix

3

Możesz nie być w stanie obejść problemu samemu, ale możesz poradzić sobie z błędem i nim sobie poradzić. Nie znam twojej konkretnej sytuacji, ale mogę sobie wyobrazić, że największym problemem jest katastrofa całej maszyny JVM. Umieszczenie wszystkich w bloku try nie działa, ponieważ nie można złapać awarii JVM.

Brak wiedzy na temat projektu sprawia, że ​​trudno jest zasugerować dobre/akceptowalne rozwiązanie, ale może to może być opcja: Wykonaj cały plik oglądając materiał w oddzielnym procesie JVM. Z głównego procesu uruchom nową maszynę JVM (np. Używając ProcessBuilder.start()). Po zakończeniu procesu (tj. Po uruchomieniu nowo uruchomionej maszyny JVM) uruchom ją ponownie. Oczywiście musisz być w stanie odzyskać, tj. Musisz śledzić, jakie pliki oglądać i musisz zachować te dane również w swoim głównym procesie.

Teraz największą pozostałą częścią jest implementacja komunikacji między procesem głównym a procesem oglądania plików. Można to zrobić za pomocą standardowego procesu oglądania pliku lub z użyciem innego mechanizmu.

4

Od komentarze:

Wydaje się, że mamy problem z I/O unieważnieniu gdy istnieje oczekiwaniu ReadDirectoryChangesW wybitne.

Oświadczenie i przykładowy kod wskazuje, że błąd jest wyzwalany, gdy:

  1. Jest toku zdarzeń, które nie zostały zużyte (to może być lub może nie być widoczny dla WatchService.poll() lub WatchService.take())
  2. WatchKey.cancel() Nazywa się na klucz

Jest to nieprzyjemny błąd bez uniwersalnego obejścia. Podejście zależy od specyfiki Twojej aplikacji. Rozważ skumulowanie zegarków w jednym miejscu, aby nie trzeba było dzwonić pod numer WatchKey.cancel(). Jeśli w pewnym momencie pula stanie się zbyt duża, zamknij całą WatchService i zacznij od początku. Coś podobnego do.

public class FileWatcerService { 
    static Kind<?>[] allEvents = new Kind<?>[] { 
     StandardWatchEventKinds.ENTRY_CREATE, 
     StandardWatchEventKinds.ENTRY_DELETE, 
     StandardWatchEventKinds.ENTRY_MODIFY 
    }; 

    WatchService ws; 

    // Keep track of paths and registered listeners 
    Map<String, List<FileChangeListener>> listeners = new ConcurrentHashMap<String, List<FileChangeListener>>(); 
    Map<WatchKey, String> keys = new ConcurrentHashMap<WatchKey, String>(); 

    boolean toStop = false; 

    public interface FileChangeListener { 
     void onChange(); 
    } 

    public void addFileChangeListener(String path, FileChangeListener l) { 
     if(!listeners.containsKey(path)) { 
      listeners.put(path, new ArrayList<FileChangeListener>()); 
      keys.put(Paths.get(path).register(ws, allEvents), path); 
     } 
     listeners.get(path).add(l); 
    } 

    public void removeFileChangeListener(String path, FileChangeListener l) { 
     if(listeners.containsKey(path)) 
      listeners.get(path).remove(l); 
    } 

    public void start() { 
     ws = FileSystems.getDefault().newWatchService(); 
     new Thread(new Runnable() { 
      public void run() { 
       while(!toStop) { 
        WatchKey key = ws.take(); 
        for(FileChangeListener l: listeners.get(keys.get(key))) 
         l.onChange(); 
       } 
      } 
     }).start(); 
    } 

    public void stop() { 
     toStop = true; 
     ws.close(); 
    } 
}