2010-06-01 8 views
9

Napisałem klasę parsera dla konkretnego formatu binarnego (nfdump, jeśli ktokolwiek jest zainteresowany), który używa MappedByteBuffer java.nio do czytania plików o wielkości kilku GB. Format binarny to po prostu seria nagłówków, a przede wszystkim stałe rekordy binarne, które są wysyłane do wywoływanego przez wywołanie nextRecord(), która przesuwa maszynę stanu, zwracając wartość null, gdy jest ona zakończona. Działa dobrze. Działa na maszynie programistycznej.Problem mapy Java/Nio/NFS powodujący błąd maszyny wirtualnej: "wystąpił błąd w niedawnej niebezpiecznej operacji dostępu do pamięci w skompilowanym kodzie Java"

Na moim hoście produkcyjnym może działać przez kilka minut lub godzin, ale zawsze wydaje się wyrzucać "java.lang.InternalError: wystąpiła usterka w ostatniej niełatwej operacji dostępu do pamięci w skompilowanym kodzie Java", dotykając jednego z metody Map.getInt, getShort, tzn. operacja odczytu na mapie. (?)

niekontrowersyjnego kod, który tworzy mapy to:

/** Set up the map from the given filename and position */ 
    protected void open() throws IOException { 
      // Set up buffer, is this all the flexibility we'll need? 
      channel = new FileInputStream(file).getChannel();  
      MappedByteBuffer map1 = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); 
      map1.load(); // we want the whole thing, plus seems to reduce frequency of crashes? 
      map = map1; 
      // assumes the host writing the files is little-endian (x86), ought to be configurable 
      map.order(java.nio.ByteOrder.LITTLE_ENDIAN); 
      map.position(position); 
    } 

a potem użyć różnych map.get * Metody czytać szorty, ints, tęskni i inne sekwencje bajtów, przed trafienie na koniec pliku i zamknięcie mapy.

Nigdy nie widziałem wyjątku rzucanego na mojego hosta rozwoju. Ale istotną różnicą między moim hostem produkcyjnym a rozwojem jest to, że czytam sekwencje tych plików przez NFS (prawdopodobnie 6-8 TB w końcu, wciąż rośnie). Na mojej maszynie deweloperskiej mam mniejszy wybór tych plików lokalnie (60 GB), ale kiedy eksploduje na hoście produkcyjnym, to zwykle na długo przed osiągnięciem 60 GB danych.

Oba urządzenia obsługują Javę 1.6.0_20-b02, chociaż hostem produkcyjnym jest Debian/Lenny, hostem dev jest Ubuntu/karmic. Nie jestem przekonany, że to coś zmieni. Oba urządzenia mają 16 GB pamięci RAM i działają z tymi samymi ustawieniami sterty java.

Uważam, że jeśli istnieje błąd w moim kodzie, jest wystarczająco dużo błędu w JVM, aby nie wyrzucić mi właściwego wyjątku! Ale myślę, że jest to po prostu szczególny błąd implementacji JVM z powodu interakcji między NFS a mmap, prawdopodobnie powtórzenie się 6244515, które jest oficjalnie naprawione.

Próbowałem już dodać wywołanie "load", aby zmusić MappedByteBuffer do załadowania jego zawartości do pamięci RAM - wydawało się, że opóźniłem ten błąd podczas jednego z przeprowadzonych testów, ale nie mogłem temu zapobiec. A może to był zbieg okoliczności, który był najdłuższy, jaki zdarzył się przed awarią!

Jeśli czytałeś tak daleko i robiłeś już takie rzeczy z java.nio wcześniej, jaki byłby twój instynkt? Teraz moje jest przepisanie go bez nio :)

+0

Zgaduję, że już widziałeś D8 (http://nfs.sourceforge.net/) – Justin

+0

Nie, dzięki, ale też nie piszę do tych plików. –

+0

Widzę to z plikami mapowanymi w pamięci w lokalnych systemach plików ext4 i tmpfs z Javą 7u1. –

Odpowiedz

4

Napisałbym to bez użycia zmapowany NIO. Jeśli masz do czynienia z więcej niż jednym plikiem, oznacza to, że zmapowana pamięć nigdy nie jest zwolniona, więc skończy Ci się pamięć wirtualna: NB to niekoniecznie tylko OutOfMemoryError, który współdziała z śmieciarem, byłby to brak alokacji nowego zmapowanego bufora. Chciałbym użyć FileChannel.

Powiedział, że operacje na dużą skalę na pliki NFS są zawsze bardzo problematyczne. Znacznie lepiej byłoby przeprojektować system, aby każdy plik był odczytywany przez jego lokalny procesor. W ten sposób uzyskasz również ogromną poprawę prędkości, o wiele bardziej niż 20%, które stracisz, nie używając zmapowanych buforów.

+0

Pomyślałem o braku wirtualnej przestrzeni adresowej, ale tak jak powiedziałeś, powinno to objawić się w niepowodzeniu mapowania (plus czytam tylko jeden plik na raz i na systemie 64-bitowym). Prawdopodobnie przestawię serwery tak, aby pliki były przechowywane na tym samym serwerze, co proces java, i unikaj wszelkich problemów z NFS. W krótkim czasie po prostu przeczytam to wszystko w ByteBuffer, ale ponieważ wiele wątków czyta te same pliki, często w tym samym czasie, jest to reimplementacja rzeczy, które mmap * powinny * być eleganckim rozwiązaniem! –

+0

Tak, miałem nadzieję na odpowiedź, która pozwoli mi zachować mmap, po prostu potrzebowałem popychania, aby ktoś inny powiedział "to nie zadziała" :) Kod open() teraz po prostu czyta całą partię w przydzielony ByteBuffer. Podczas gdy mój instynkt miał się martwić o marnotrawstwo pamięci (jako że kilku czytelników = kilka kopii na kupie), nie widziałem spadku wydajności w porównaniu z poprzednimi przebiegami, więc nie mogę narzekać. Zostawiłem stary kod skomentowany w nadziei, że uda mi się przywrócić "elegancki" mmap, ale zakładając, że moje pliki nfdump pozostaną niezmienione, prawdopodobnie nie będę tego potrzebować ponownie. –

+0

"kilku czytelników = kilka kopii na stercie": tylko jeśli wykonasz te kilka kopii. Nie możesz zorganizować jakiegoś dostępu do singletonu? – EJP

Powiązane problemy