Pamięć w Javie

0

Tworzę projekt w Netbeans.

Zauważyłem, że we właściwościach projektu można ustawić początkową i maksymalną wielkośc sterty, ale zauważyłem dziwną rzecz:

Jak ustawię "-Xms16m -Xmx1600m", to program nie uruchamia się i wyświetla komunikat:

Error occurred during initialization of VM
Could not create the Java virtual machine.
Could not reserve enough space for object heap
Java Result: 1

Jak ustawię "-Xms16m -Xmx800m", to program się uruchamia. Cały program, to jest wyświetlenie "Hello world" na konsoli, więc program nie tworzy jakiś nie wiadomo, jak dużych obiektów.

Dziwne jest to, że w obu przypadkach wielkość początkowa jest stała, a zmieniam wartość maksymalną, którą można przeznaczyć na potrzeby sterty programu Javy.

Jestem przekonany, że jak ustawię "-Xms16m -Xmx1600m", to na innym komputerze z większą pamięcią RAM program również się uruchomi.

Jak ustawić parametry uruchamiania, żeby spełnić następujące założenia:

  • Wielkość sterty nie jest ograniczona, jedynym ograniczeniem jest wielkość pamięci RAM w danym komputerze
  • Program zajmuje tylko tyle pamięci RAM, ile faktycznie jest obiektów
  • Nie jest istotna wielkość pamięci w momencie uruchomienia programu, problemy z działaniem byłyby dopiero przy próbie utworzenia obiektu przy pełnej pamięci RAM
  • Jeżeli jest kilka uruchomionych programów w Javie jednocześnie na tym samym komputerze, to każdy może potencjalnie wykorzystać całą pamięć RAM.

W językach C++ i C# każdy jeden program spełnia powyższe założenia.

Rozbudowałem procedurę Main do takiej postaci:

    public static void main(String[] args)
    {
        ArrayList<byte[]> Lista = new ArrayList<byte[]>();
        int XX = 0;
        while (true)
        {
            System.out.println(XX);
            Lista.add(new byte[100000000]);
            XX++;
        }
    }

Przy ustawieniu "-Xmx500m" widać, że program tworzy 5 tablic bajtowych zajmujących niecałe 100MB każda, a przy "-Xmx800m" program tworzy 8 tablic bajtowych, a jak skasuję parametry uruchamiania, to program tworzy tylko dwie tablice bajtowe. Jak ustawiłem "-Xmx1200m", to program się uruchomił, ale powstało tylko 9 tablic, a w menedżerze zadań pamięć rzeczywiście się wypełniła. Przypuszczam, że na innym komputerze przy "-Xmx1200m" powstanie 12 tablic, a przy ustawieniu "-Xmx2000m" program się również uruchomi i utworzy 20 tablic bajtowych. Jednak na moim komputerze z takim ustawieniem program w ogóle się nie uruchamia.

Chciałbym, żeby ten program uruchomił się na każdym komputerze bez względu na wielkość zainstalowanej pamięci i utworzył tyle tablic bajtowych, ile da radę do zapełnienia pamięci RAM. Jak ustawić parametry uruchamiania? Jakbym pisał ten program w C++ lub C#, to taki problem w ogóle by nie istniał.

0

Linux/ Windows? 32-bit / 64-bit?

W robocie pracuję nad dużą aplikacją Javową i generalnie dla 32-bitowych JVMek na Windowsie ustala się max jakieś 1200m - 1400m (Windows jakoś na więcej nie pozwoli), natomiast na Linuksie można spokojnie dać np 3000m (ciągle w 32-bitach).

Poza tym na Ubuntu 64-bit i 32 GiB RAMu zrobiłem sobie eksperyment. Odpaliłem twój program z parametrem java -Xmx50000m Main (tak, mx był większy niż dostępny RAM), i zaalokowało 320 bloków po 100 milionów bajtów, więc w zasadzie zapchało pamięć do końca. A potem JVMka się wyłożyła z crash reportem. Tak więc generalnie JVMka zajmuje zdecydowanie mniej niż Xmx, o ile nie zapchasz sterty, ale przy braku pamięci JVMka się po prostu wysypuje zamiast posłać OutOfMemoryError - chociaż ja mam wyłączonego swapa, być może ze swapem nie byłoby crasha?

Jest RFE dotyczące twojego problemu: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4408373
Odpowiedź:

EVALUATION

It is unlikely that we will eliminate the -Xmx ceiling any time soon.
We get a lot of performance out of having side data structures that
are 1-to-1 with the heap, so we need the heap to be contiguous.

We know how to build "chunked" heaps, but the performance is thought
to be significant (10%?), to allow for a feature that you mostly
won't use if we can size the heap correctly from command line.

This is not high on our list.

Rozwiązaniem, nieco karkołomnym, jest dynamiczne odpalanie kolejnych JVMek.

0

@Wibowit z tym windowsem to nie jest prawda ;] Ja na swoim laptopie spokojnie mogę odpalić javę z Xmx3g i pomyka jak rącza gazela. Co więcej potrzebowałem tak robić jak robiłem obliczenia do magisterki bo wymagały właśnie mniej wiecej tyle pamięci.

0

U mnie jest to Windows XP 32-bit. Pierwszy raz zauważyłem problem w momencie, gdy program działający w Windows sprawiał problemy w MacOS rzucając wyjątek na temat braku pamięci w trakcie działania.

@Wibowit: Czy w robocie robicie jeden plik JAR i na Windowsie 32-bit uruchamiacie poleceniem:
java -jar -Xmx1200m ConsoleTest.jar
A w pozostałych przypadkach uruchamiacie poleceniem:
java -jar -Xmx3000m ConsoleTest.jar

Zauważyłem, że NetBeans nie umie zapisać w Jarze generowanym w katalogu "dist" domyslnych parametrów uruchamiania i przy uruchamianiu bez podania Xmx z cmd.exe poza netBeans zakłada tylko dwie tablice bez wzgledu na ustawienia projektu.

Moim zdaniem Swap jest tylko przedłużeniem pamięci RAM i jeżeli masz system 32-bitowy, to program się wysypie po zajęciu troche ponad 3GB RAM. Przynajmniej tak było w moim przypadku z programami w C++ i C# w Windows XP 32-bit. Tutaj barierą jest adresowanie 32-bitowe.

Rozszerzyłem program testowy o pokazywanie informacji o pamięci, ale ilośc maksymalna to ilość nieco pomniejszona oz Xmx a nie rzeczywista. Natomiast całkowita przy kilku iteracjach jest ta sama, a tablice nie mogą ginąć pod wpływem działania GC, bo zapisuję je na liście.

    public static void main(String[] args)
    {
        ArrayList<byte[]> Lista = new ArrayList<byte[]>();
        int XX = 0;
        while (true)
        {
            System.out.print(XX);
            System.out.print("   Total:" + Runtime.getRuntime().totalMemory());
            System.out.print("   Max:" + Runtime.getRuntime().maxMemory());
            System.out.print("   Free:" + Runtime.getRuntime().freeMemory());
            System.out.println();
            Lista.add(new byte[100000000]);
            XX++;
        }
    }
run:
0   Total:16252928   Max:1317732352   Free:15963280
1   Total:116322304   Max:1317732352   Free:16106528
2   Total:342048768   Max:1317732352   Free:140587792
3   Total:483684352   Max:1317732352   Free:180890352
4   Total:483684352   Max:1317732352   Free:82190592
5   Total:967028736   Max:1317732352   Free:465099832
6   Total:967028736   Max:1317732352   Free:365099816
7   Total:967028736   Max:1317732352   Free:265714824
8   Total:967028736   Max:1317732352   Free:165714808
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at consoletest.ConsoleTest.main(ConsoleTest.java:34)
Java Result: 1
0

Czy w robocie robicie jeden plik JAR i na Windowsie 32-bit uruchamiacie poleceniem:
java -jar -Xmx1200m ConsoleTest.jar
A w pozostałych przypadkach uruchamiacie poleceniem:
java -jar -Xmx3000m ConsoleTest.jar

Jest to aplikacja EJB, więc to nie jest jeden JAR, ale Xmx jest mniej więcej w ten sposób ustawiany. Dokładnie to jest plik .sh z parametrami, które potem są doklejane do parametrów serwera aplikacyjnego. W skrócie to jednak ogranicza się do tego co napisałeś.

Zauważyłem, że NetBeans nie umie zapisać w Jarze generowanym w katalogu "dist" domyslnych parametrów uruchamiania i przy uruchamianiu bez podania Xmx z cmd.exe poza netBeans zakłada tylko dwie tablice bez wzgledu na ustawienia projektu.

No bo w JARze nie da się tego zapisać tak by java.exe to wzięło pod uwagę. Trzeba zrobić plik .sh i/ lub .bat.

Rozszerzyłem program testowy o pokazywanie informacji o pamięci, ale ilośc maksymalna to ilość nieco pomniejszona oz Xmx a nie rzeczywista.

Bo część jest przeznaczona np na PermGena lub np stosy dla wątków.

Natomiast całkowita przy kilku iteracjach jest ta sama, a tablice nie mogą ginąć pod wpływem działania GC, bo zapisuję je na liście.

Jak się dobrze przyjrzysz, to zauważysz że Java nie zwiększa sobie sterty co 100 MB, tylko o więcej i to dlatego nie musi zwiększać jej za każdą iteracją.

Ogólnie sterta w Javie jest podzielona na wiele generacji, np Eden, Survivor1, Survivor2, Old, Perm. Obiekt nie może znajdować się w kilku generacjach jednocześnie, np kawałek w Eden, a kawałek w Survivor1. Tablica to też obiekt, więc cała musi znajdować się w jednej generacji. Żeby upchać więcej tablic niż aktualnie możesz upchać, musiałbyś tak ustawić rozmiary generacji, żeby mieć jak najmniej pamięci niewykorzystanej.

1 użytkowników online, w tym zalogowanych: 0, gości: 1