Systemy operacyjne wykonują operacje we / wy w obszarach pamięci. Z punktu widzenia systemu operacyjnego te obszary pamięci są ciągłymi sekwencjami bajtów. Nie jest więc zaskoczeniem, że tylko bufory bajtów mogą uczestniczyć w operacjach we / wy. Przypomnijmy również, że system operacyjny będzie miał bezpośredni dostęp do przestrzeni adresowej procesu, w tym przypadku procesu JVM, w celu przesłania danych. Oznacza to, że obszary pamięci, które są celami operacji we / wy, muszą być ciągłymi sekwencjami bajtów. W JVM tablica bajtów może nie być przechowywana w pamięci ciągłej lub moduł Garbage Collector może ją przenieść w dowolnym momencie. Tablice to obiekty w Javie, a sposób przechowywania danych w tym obiekcie może się różnić w zależności od implementacji maszyny JVM.
Z tego powodu wprowadzono pojęcie bufora bezpośredniego. Bufory bezpośrednie są przeznaczone do interakcji z kanałami i natywnymi procedurami we / wy. Dokładają wszelkich starań, aby przechowywać elementy bajtowe w obszarze pamięci, którego kanał może używać do bezpośredniego lub surowego dostępu, używając kodu natywnego, aby nakazać systemowi operacyjnemu opróżnienie lub bezpośrednie wypełnienie obszaru pamięci.
Bezpośrednie bufory bajtowe są zwykle najlepszym wyborem dla operacji we / wy. Z założenia obsługują najbardziej wydajny mechanizm we / wy dostępny dla maszyny JVM. Niebezpośrednie bufory bajtów mogą być przekazywane do kanałów, ale może to spowodować spadek wydajności. Zwykle nie jest możliwe, aby niebezpośredni bufor był celem natywnej operacji we / wy. Jeśli przekażesz niebezpośredni obiekt ByteBuffer do kanału w celu zapisu, kanał może niejawnie wykonać następujące czynności przy każdym wywołaniu:
- Utwórz tymczasowy bezpośredni obiekt ByteBuffer.
- Skopiuj zawartość niebezpośredniego bufora do bufora tymczasowego.
- Wykonaj niskopoziomową operację we / wy przy użyciu tymczasowego bufora.
- Tymczasowy obiekt bufora wykracza poza zakres i ostatecznie jest usuwany z pamięci.
Może to potencjalnie skutkować kopiowaniem buforów i rezygnacją z obiektów na każdym I / O, czyli dokładnie tego rodzaju rzeczy, których chcielibyśmy uniknąć. Jednak w zależności od implementacji sytuacja może nie wyglądać tak źle. Środowisko wykonawcze prawdopodobnie buforuje i ponownie wykorzystuje bufory bezpośrednie lub wykonuje inne sprytne sztuczki, aby zwiększyć przepustowość. Jeśli po prostu tworzysz bufor do jednorazowego użytku, różnica nie jest znacząca. Z drugiej strony, jeśli będziesz używać bufora wielokrotnie w scenariuszu o wysokiej wydajności, lepiej jest przydzielić bufory bezpośrednie i ponownie ich używać.
Bufory bezpośrednie są optymalne dla operacji we / wy, ale ich tworzenie może być droższe niż niebezpośrednie bufory bajtowe. Pamięć używana przez bufory bezpośrednie jest przydzielana przez wywołanie natywnego kodu specyficznego dla systemu operacyjnego, z pominięciem standardowej sterty maszyny JVM. Konfigurowanie i niszczenie bezpośrednich buforów może być znacznie droższe niż bufory rezydentne, w zależności od systemu operacyjnego hosta i implementacji maszyny JVM. Obszary pamięci buforów bezpośrednich nie podlegają czyszczeniu pamięci, ponieważ znajdują się poza standardową stertą maszyny JVM.
Kompromisy wydajnościowe wynikające z używania buforów bezpośrednich i niebezpośrednich mogą się znacznie różnić w zależności od maszyny JVM, systemu operacyjnego i projektu kodu. Alokując pamięć poza stertą, można poddać aplikację dodatkowym siłom, o których JVM nie jest świadoma. Wprowadzając do gry dodatkowe ruchome części, upewnij się, że osiągasz pożądany efekt. Polecam starą maksymę dotyczącą oprogramowania: najpierw spraw, by działało, a potem szybko. Nie przejmuj się zbytnio optymalizacją z góry; skoncentruj się najpierw na poprawności. Implementacja JVM może być w stanie wykonać buforowanie bufora lub inne optymalizacje, które zapewnią wymaganą wydajność bez zbędnego wysiłku z Twojej strony.