Jest to od dawna skarga na Javę, ale jest w dużej mierze bez znaczenia i zwykle opiera się na spojrzeniu na niewłaściwe informacje. Zwykłe frazowanie to coś w stylu „Hello World na Javie zajmuje 10 megabajtów! Dlaczego to potrzebne?” Oto sposób, aby Hello World na 64-bitowej maszynie JVM przejął 4 gigabajty ... przynajmniej przez jedną formę pomiaru.
java -Xms1024m -Xmx4096m com.example.Hello
Różne sposoby pomiaru pamięci
W systemie Linux górne polecenie podaje kilka różnych liczb pamięci. Oto, co mówi o przykładzie Hello World:
PID USER PR NI VIRT RES SHR S% CPU% MEM TIME + COMMAND
2120 kgregory 20 0 4373m 15m 7152 S 0 0,2 0: 00,10 java
- VIRT to przestrzeń pamięci wirtualnej: suma wszystkiego na mapie pamięci wirtualnej (patrz poniżej). Jest w dużej mierze bez znaczenia, z wyjątkiem sytuacji, gdy nie jest (patrz poniżej).
- RES jest rezydentnym rozmiarem zestawu: liczbą stron, które są obecnie rezydentem w pamięci RAM. W prawie wszystkich przypadkach jest to jedyna liczba, której należy użyć, mówiąc „za duży”. Ale wciąż nie jest to zbyt dobra liczba, szczególnie jeśli chodzi o Javę.
- SHR to ilość rezydentnej pamięci współdzielonej z innymi procesami. W przypadku procesu Java jest to zwykle ograniczone do bibliotek współdzielonych i plików JAR zamapowanych w pamięci. W tym przykładzie działał tylko jeden proces Java, więc podejrzewam, że 7k jest wynikiem bibliotek używanych przez system operacyjny.
- SWAP nie jest domyślnie włączony i nie jest tu pokazywany. Wskazuje ilość pamięci wirtualnej aktualnie rezydującej na dysku, niezależnie od tego , czy faktycznie znajduje się w przestrzeni wymiany . System operacyjny bardzo dobrze trzyma aktywne strony w pamięci RAM, a jedynym sposobem na zamianę jest (1) zakup większej ilości pamięci lub (2) zmniejszenie liczby procesów, więc najlepiej zignorować tę liczbę.
Sytuacja Menedżera zadań Windows jest nieco bardziej skomplikowana. W systemie Windows XP są kolumny „Wykorzystanie pamięci” i „Rozmiar pamięci wirtualnej”, ale oficjalna dokumentacja milczy na ich temat. Windows Vista i Windows 7 dodają więcej kolumn i są one faktycznie udokumentowane . Spośród nich najbardziej użyteczny jest pomiar „zestawu roboczego”; w przybliżeniu odpowiada sumie RES i SHR w systemie Linux.
Zrozumienie wirtualnej mapy pamięci
Pamięć wirtualna zużywana przez proces jest sumą wszystkiego, co znajduje się na mapie pamięci procesu. Obejmuje to dane (np. Stertę Java), ale także wszystkie biblioteki współdzielone i pliki mapowane w pamięci używane przez program. W systemie Linux możesz użyć polecenia pmap, aby zobaczyć wszystkie rzeczy zmapowane w przestrzeni procesu (od tej pory będę odnosił się tylko do Linuksa, ponieważ tego właśnie używam; jestem pewien, że istnieją równoważne narzędzia do Windows). Oto fragment mapy pamięci programu „Hello World”; cała mapa pamięci ma ponad 100 linii i nie jest niczym niezwykłym posiadanie listy tysiąca linii.
0000000040000000 36K rx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000 8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000 676K rwx-- [anon]
00000006fae00000 21248K rwx-- [anon]
00000006fc2c0000 62720K rwx-- [anon]
0000000700000000 699072K rwx-- [anon]
000000072aab0000 2097152K rwx-- [anon]
00000007aaab0000 349504K rwx-- [anon]
00000007c0000000 1048576K rwx-- [anon]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [anon]
00007fa1ed2d3000 4K ----- [anon]
00007fa1ed2d4000 1024K rwx-- [anon]
00007fa1ed3d4000 4K ----- [anon]
...
00007fa1f20d3000 164K rx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000 28K rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000 1576K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so
...
Szybkie wyjaśnienie formatu: każdy wiersz zaczyna się od adresu pamięci wirtualnej segmentu. Po tym następuje rozmiar segmentu, uprawnienia i źródło segmentu. Ten ostatni element to albo plik, albo „anon”, co oznacza blok pamięci przydzielany przez mmap .
Zaczynając od góry, mamy
- Moduł ładujący JVM (tzn. Program uruchamiany podczas pisania
java
). To jest bardzo małe; wszystko, co robi, to ładowanie do bibliotek współdzielonych, w których przechowywany jest prawdziwy kod JVM.
- Kilka bloków anonowych zawierających stos Javy i dane wewnętrzne. Jest to Sun JVM, więc sterta jest podzielona na wiele generacji, z których każda jest własnym blokiem pamięci. Zauważ, że JVM przydziela przestrzeń pamięci wirtualnej na podstawie
-Xmx
wartości; pozwala to mieć ciągłą stertę. Ta -Xms
wartość jest używana wewnętrznie do określenia, ile sterty jest „w użyciu” podczas uruchamiania programu, oraz do wyzwalania wyrzucania elementów bezużytecznych w miarę zbliżania się tego limitu.
- Plik JAR odwzorowany w pamięci, w tym przypadku plik zawierający „klasy JDK”. Kiedy mapujesz pamięć JAR, możesz bardzo wydajnie uzyskiwać dostęp do plików w nim zawartych (w porównaniu z odczytywaniem go od początku za każdym razem). Sun JVM zmapuje w pamięci wszystkie pliki JAR w ścieżce klasy; jeśli twój kod aplikacji musi uzyskać dostęp do pliku JAR, możesz go również zmapować w pamięci.
- Dane dla wątku dla dwóch wątków. Blok 1M to stos wątków. Nie miałem dobrego wyjaśnienia dla bloku 4k, ale @ericsoe zidentyfikował go jako „blok ochronny”: nie ma uprawnień do odczytu / zapisu, więc spowoduje błąd segmentu, jeśli zostanie uzyskany dostęp, a JVM złapie to i tłumaczy to do
StackOverFlowError
. W przypadku prawdziwej aplikacji zobaczysz dziesiątki, jeśli nie setki tych wpisów powtórzonych przez mapę pamięci.
- Jedna z bibliotek współdzielonych, która przechowuje aktualny kod JVM. Jest ich kilka.
- Biblioteka współdzielona dla biblioteki standardowej C. To tylko jedna z wielu rzeczy, które ładuje JVM, które nie są ściśle częścią Javy.
Współużytkowane biblioteki są szczególnie interesujące: każda wspólna biblioteka ma co najmniej dwa segmenty: segment tylko do odczytu zawierający kod biblioteki oraz segment do odczytu i zapisu, który zawiera globalne dane dla poszczególnych procesów dla biblioteki (nie wiem, co segment bez uprawnień to; Widziałem go tylko na Linuksie x64). Część biblioteki tylko do odczytu może być współużytkowana przez wszystkie procesy korzystające z biblioteki; na przykład libc
ma 1,5 mln pamięci wirtualnej, którą można współdzielić.
Kiedy rozmiar pamięci wirtualnej jest ważny?
Mapa pamięci wirtualnej zawiera wiele rzeczy. Niektóre z nich są tylko do odczytu, niektóre są udostępniane, a niektóre są przydzielane, ale nigdy nie zostały zmienione (np. Prawie wszystkie 4 Gb stosu w tym przykładzie). Ale system operacyjny jest wystarczająco inteligentny, aby załadować tylko to, czego potrzebuje, więc rozmiar pamięci wirtualnej jest w dużej mierze nieistotny.
Ważna jest wielkość pamięci wirtualnej, jeśli używasz 32-bitowego systemu operacyjnego, w którym możesz przydzielić tylko 2 Gb (lub, w niektórych przypadkach, 3Gb) przestrzeni adresowej procesu. W takim przypadku masz do czynienia z ograniczonym zasobem i być może będziesz musiał dokonać kompromisów, takich jak zmniejszenie wielkości sterty w celu mapowania pamięci dużego pliku lub utworzenia wielu wątków.
Ale biorąc pod uwagę, że 64-bitowe maszyny są wszechobecne, nie sądzę, że minie dużo czasu, zanim Rozmiar pamięci wirtualnej stanie się zupełnie nieistotną statystyką.
Kiedy Resident Set Size jest ważny?
Rozmiar zestawu rezydentnego to część przestrzeni pamięci wirtualnej, która faktycznie znajduje się w pamięci RAM. Jeśli Twój RSS staje się znaczną częścią całkowitej pamięci fizycznej, być może nadszedł czas, aby zacząć się martwić. Jeśli Twój RSS rośnie i zajmuje całą twoją pamięć fizyczną, a twój system zaczyna się zamieniać, nadszedł czas, aby zacząć się martwić.
Ale RSS również wprowadza w błąd, szczególnie na lekko obciążonej maszynie. System operacyjny nie wymaga wiele wysiłku, aby odzyskać strony używane przez proces. Dzięki temu nie zyskasz zbyt wiele, a możliwość wystąpienia kosztownej usterki strony, jeśli proces dotknie strony w przyszłości. W rezultacie statystyki RSS mogą obejmować wiele stron, które nie są aktywnie używane.
Dolna linia
O ile nie dokonujesz wymiany, nie przejmuj się zbytnio tym, co mówią różne statystyki pamięci. Z zastrzeżeniem, że stale rosnący RSS może wskazywać na pewien wyciek pamięci.
W programie Java znacznie ważniejsze jest zwrócenie uwagi na to, co dzieje się na stosie. Ważna jest łączna ilość zajętego miejsca i można podjąć pewne kroki, aby ją zmniejszyć. Ważniejsze jest to, ile czasu spędzasz na zbieraniu śmieci i jakie części sterty są zbierane.
Dostęp do dysku (tj. Bazy danych) jest drogi, a pamięć tania. Jeśli możesz wymienić jeden na drugi, zrób to.