Paskudny problem z pamięcią

0

Teraz mój problemik, w którym może pomoże mi ktoś obryty z Javą.
Do swojego programu potrzebuję nieco więcej pamięci RAM. Wydaje mi się jednak, że standardowe ustawienie Javy na sztywno ustala jej rozmiar do domyślnego rozmiaru 64 MB, co jest na komputerach z 2-4 GB zainstalowanej pamięci jakąś piramidalną bzdurą. Nie chciało mi się wierzyć, że Sun odpierniczył taką chałę, ale w tym celu musiałem sobie zmajstrować programik, który mi to sprawdził.

Moje najgorsze przeczucia się potwierdziły. Choćby system miał nie wiadomo ile RAMu, to JRE pozwala przydzielić tylko tyle pamięci ile ma sztywno ustawione jako maks. w opcjach konfiguracyjnych na lokalnym kompie.

Pytanie jest takie - czy da się to jakoś obejść, tak aby nie zmuszać osoby odpalającej program, a w szczególności odpalającej aplet, żeby przydzielić dla JVM możliwie największą ilość pamięci możliwej do potencjalnego użycia przez mój program? Albo spowodować, żeby ilość tej pamięci była dynamicznie zależna od ilości fizycznej pamięci RAM na lokalnym komputerze?
Ze zmienną ilością pamięci do dyspozycji sobie poradzę, ale to domyślne ograniczenie 64 MB po prostu mnie dobija.

Przy okazji sprawdzania ile można przydzielić pamięci wykryłem coś bardzo dziwnego. Wszelkie rodzaje obiektów i tablic z prymitywami mogłem zajmować do granicy 64 MB, ale działając w tych samych warunkach program, którego kod podaję dalej wykazuje mi coś niemożliwego - że mogę przydzielić jak mi się wydaje unikalnym obiektom String więcej pamięci niż 2 GB RAM!
Program mi raportuje takie coś:

Pamięć maksymalna 63,56 MB


Zakończono zdzieranie pamięci dla Kandydaci.Zdzieracz_String.
Zdzieranie przerwano z powodu wyczerpania pamięci.
Rozmiar Zdzieracza: 16 B
Rozmiar elementu: 72 B
Przydzielono 33133202 elementów
zajmujących łącznie 2,34 GB pamięci RAM w 4276 przydziałach.
Pamięć przydzielona elementom to 2,22 GB
Narzut rozmiaru tablic obiektów: 126,39 MB

Dla porównania jeszcze 2 raporty dla innych rodzajów danych:
a)
Pamięć maksymalna 63,56 MB


Zakończono zdzieranie pamięci dla Kandydaci.Zdzieracz_Long.
Zdzieranie przerwano z powodu wyczerpania pamięci.
Rozmiar Zdzieracza: 16 B
Rozmiar elementu: 16 B
Przydzielono 3320184 elementów
zajmujących łącznie 63,33 MB pamięci RAM w 97 przydziałach.
Pamięć przydzielona elementom to 50,66 MB
Narzut rozmiaru tablic obiektów: 12,67 MB
b)
Pamięć maksymalna 63,56 MB


Zakończono zdzieranie pamięci dla Kandydaci.Zdzieracz_char.
Zdzieranie przerwano z powodu wyczerpania pamięci.
Rozmiar Zdzieracza: 16 B
Rozmiar elementu: 2 B
Przydzielono 33201528 elementów
zajmujących łącznie 63,33 MB pamięci RAM w 105 przydziałach.
Pamięć przydzielona elementom to 63,33 MB
Narzut rozmiaru tablic obiektów: 0 B

Tak więc albo Java używa dla klasy String jakiejś cudownej kompresji, albo czegoś o Stringach nie wiem i przez to robię błąd w programie, którego kod podaję niżej:

package Kandydaci;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import javax.swing.SwingUtilities;

/**
 * Wyczerpuje pamięć RAM do oporu
 * @author Olamagato
 */
public class WyciskRAM
{
	public final boolean POKAŻ_SZCZEGÓŁY = false;//wypisywanie szczegółów
	public final int ILOŚĆ_POBRAŃ_MAX = 8192;//maksymalna ilość zdzieraczy
	public final int DZIELNIK_PAMIĘCI = 5;//mianownik ułamka wolnej pamięci
	public final int ZABEZPIECZENIE = 1024;//w bajtach
	public final int ODSTĘP_CZASU = 20;//w milisekundach
	
	public static void main(String[] args)
	{
		Zdzieracz[] zdzieracze =
		{
			new Zdzieracz_Object(),
//			new Zdzieracz_Long(),
//			new Zdzieracz_BufferedImage(),
//			new Zdzieracz_int(),
//			new Zdzieracz_char(),
//			new Zdzieracz_String(),
		};
		for( Zdzieracz zdzieracz:zdzieracze )
			new WyciskRAM(zdzieracz).wyciskaj();
	}

	private interface ProducentObiektu { Object twórzObiekt(); }
	
	/**
	 * Tworzy obiekt wyczerpujący maksymalną ilość pamięci
	 * @param rodzaj typ Zdzieracza
	 */
	public WyciskRAM(final Zdzieracz rodzaj)
	{
		zdzieraczRAM = new Zdzieracz[ILOŚĆ_POBRAŃ_MAX];
		this.typ = rodzaj;
		rozmiarZdzieracza = rozmiarObiektu(new ProducentObiektu()
		{
			public Object twórzObiekt()
			{ return rodzaj.twórzZdzieracza(); }
		});
		//przypisuje rozmiar elementu Zdzieracza
		rozmiarElementu = rozmiarObiektu(new ProducentObiektu()
		{
			public Object twórzObiekt() { return rodzaj.twórzElement(); }
		});
	}
	protected synchronized int rozmiarObiektu(ProducentObiektu fabryka)
	{
		if(fabryka == null)
			return 0; //brak obiektu, rozmiar zerowy
		long mem0, mem1;
		Object obiekt;
		//ustabilizowanie śmieciarki
		obiekt = fabryka.twórzObiekt();
		mem0 = totalMem() - freeMem();
		mem1 = totalMem() - freeMem();
		obiekt = null;
		for(int i = 0; i < 16; i++)
			System.gc();
		mem0 = totalMem() - freeMem();
		//sprawdzenie pamięci
		obiekt = fabryka.twórzObiekt();
		for(int i = 0; i < 16; i++)
			System.gc();
		mem1 = totalMem() - freeMem();
		obiekt = null;
		return (int)(mem1 - mem0);
	}
	
	public void wyciskaj()
	{
		if(zdzieraczRAM == null)
			return;
		System.err.printf("Pamięć maksymalna %s\n",
			Tool.memorySize(maxMem()));
		Tool.sleep(500);
		int ostatnie = 0;//indeks ostatniego zdarcia pamięci
		int poprzednie = 0;
		int licznikPrób = 10; //ilość wykonań pętli bez żadnego efektu
		int doPobrania = 0;
		long wolna = 0;
		
		while( ostatnie < zdzieraczRAM.length
			&& licznikPrób > 0
			&& (wolna = freeMem()) > ZABEZPIECZENIE )
		{
			if(POKAŻ_SZCZEGÓŁY)
				System.out.printf(//"Pamięć całkowita: %s\n" +
					"Wolna pamięć: %s\n",
					Tool.memorySize(totalMem()),
					Tool.memorySize(wolna));
			doPobrania = (int)(wolna / DZIELNIK_PAMIĘCI);
			
			if(doPobrania < (ZABEZPIECZENIE) && POKAŻ_SZCZEGÓŁY)
					System.err.printf(
						"Pamięć do pobrania mniejsza niż %s\n",
						Tool.memorySize(ZABEZPIECZENIE) );
			try
			{
				Zdzieracz nowy = typ.twórzZdzieracza();
				nowy.pobierzRAM(doPobrania,
					rozmiarZdzieracza, rozmiarElementu);
				zdzieraczRAM[ostatnie] = nowy;
			}
			catch(OutOfMemoryError e)
			{
				System.err.println("Wyjątek braku pamięci!");
				System.err.println("Ostatnia pamięć: "
				+ (zdzieraczRAM[ostatnie] != null ? " " : " nie ")
				+ "przydzielona.");
				System.err.println("Zostało prób: " + licznikPrób--);
				continue;
			}
			if(zdzieraczRAM[ostatnie] == null)
			{
				System.err.println("Brak przydzielonej pamięci"
					+ " (bez wyrzucenia wyjątku)");
				break;
			}
			else
			{
				if(POKAŻ_SZCZEGÓŁY)
					System.out.printf("Kolejne zdarcie: %s\n",
						Tool.memorySize(zdzieraczRAM[ostatnie]
						.pobranyRAM(rozmiarZdzieracza,
						rozmiarElementu)));
				++ostatnie;
			}
			if(poprzednie == ostatnie) //nie zdarto pamięci
					--licznikPrób;
			else
				licznikPrób = 10; //reset licznika 
			poprzednie = ostatnie;
			Tool.sleep(ODSTĘP_CZASU);
		}
		
		//Statystyka
		long zdarto = 0;
		long elementy = 0;
		for(int i = 0; i < ostatnie; ++i)
		{
			elementy += zdzieraczRAM[i].pobraneElementy();
			zdarto += zdzieraczRAM[i]
				.pobranyRAM(rozmiarZdzieracza, rozmiarElementu);
		}
		zdzieraczRAM = null;
		System.gc(); //zwolnienie pamięci dla następnych linijek
		Tool.sleep(500);
		
		long rozmiaryTablic = 0; //dla nieobiektów
		if(rozmiarElementu > 0)
			rozmiaryTablic = Zdzieracz.ROZMIAR_TABLICY
				+ Zdzieracz.ROZMIAR_REFERENCJI * elementy; //dla obiektów
		else
			rozmiarElementu = typ.rozmiarElementu();
		
		System.out.printf("*************************************\n"
		+ "Zakończono zdzieranie pamięci dla %s.\n"
		+ "Zdzieranie przerwano %s\n"
		+ "Rozmiar Zdzieracza: %s\n"
		+ "Rozmiar elementu: %s\n"
		+ "Przydzielono %d elementów\n"
		+ "zajmujących łącznie %s pamięci RAM w %d przydziałach.\n"
		+ "Pamięć przydzielona elementom to %s\n"
		+ "Narzut rozmiaru tablic obiektów: %s\n",
			typ.getClass().getName(),
			licznikPrób > 0 ? "z powodu wyczerpania pamięci."
				: "z pieprzonego braku efektów!",
			Tool.memorySize(rozmiarZdzieracza),
			Tool.memorySize(rozmiarElementu),
			elementy,
			Tool.memorySize(zdarto), ostatnie,
			Tool.memorySize(zdarto - rozmiaryTablic),
			Tool.memorySize(rozmiaryTablic));
	}
	//skróty
	private long freeMem() { return Runtime.getRuntime().freeMemory(); }
	private long totalMem() { return Runtime.getRuntime().totalMemory(); }
	private long maxMem() { return Runtime.getRuntime().maxMemory(); }

	private Zdzieracz typ;
	private int rozmiarZdzieracza;
	private int rozmiarElementu;
	public volatile Zdzieracz[] zdzieraczRAM;//zablokowana pamięć
}

/** Klasa o zmiennym rozmiarze */
abstract class Zdzieracz
{
	//przydzielenie bajtów pamięci
	public final void pobierzRAM(int bajty,
		int rozmiarZdzieracza, int rozmiarElementu)
	{
		bajty -= rozmiarZdzieracza + ROZMIAR_TABLICY;
		int elementyTablic = rozmiarElementu > 0
			? rozmiarElementu + ROZMIAR_REFERENCJI //dla obiektów
			: rozmiarElementu(); //dla prostych danych
		int doPobrania = bajty / elementyTablic;
		//pobieramy zawsze conajmniej 1 element bez wzgl. na freeMem()
		pobierzElementy(doPobrania > 0 ? doPobrania : 1); 
	}
	//ilość przydzielonej pamięci w bajtach
	public final int pobranyRAM(int rozmiarZdzieracza, int rozmiarElementu)
	{
		int elementyTablic = rozmiarElementu > 0
			? rozmiarElementu + ROZMIAR_REFERENCJI //dla obiektów
			: rozmiarElementu(); //dla prostych danych
		return rozmiarZdzieracza + ROZMIAR_TABLICY + pobraneElementy()
			* elementyTablic;
	}
	
	protected abstract Zdzieracz twórzZdzieracza();
	protected abstract Object twórzElement();
	protected int rozmiarElementu() { return 0; }
	//przydzielenie jakiejś liczby obiektów
	protected abstract void pobierzElementy(int ilość);
	//ilość przydzielonych obiektów
	public abstract int pobraneElementy();

	public final static int ROZMIAR_TABLICY = 16;
	public final static int ROZMIAR_REFERENCJI = 4;
}

class Zdzieracz_Long extends Zdzieracz
{
	public Zdzieracz twórzZdzieracza() { return new Zdzieracz_Long(); }
	public Object twórzElement() { return new Long(-1); }
	protected void pobierzElementy(int ilość)
	{
		zassane = new Long[ilość];
		for( int i = 0; i < zassane.length; i++ )
			zassane[i] = (Long) twórzElement();
	}
	public int pobraneElementy() { return zassane.length; }
	private Long[] zassane;
}
class Zdzieracz_Object extends Zdzieracz
{
	public Zdzieracz twórzZdzieracza() { return new Zdzieracz_Object(); }
	public Object twórzElement() { return new Object(); }
	protected void pobierzElementy(int ilość)
	{
		zassane = new Object[ilość];
		for( int i = 0; i < zassane.length; i++ )
			zassane[i] = twórzElement();
	}
	public int pobraneElementy() { return zassane.length; }
	private Object[] zassane;
}
class Zdzieracz_BufferedImage extends Zdzieracz
{
	@Override public Zdzieracz twórzZdzieracza()
	{ return new Zdzieracz_BufferedImage(); }
	
	public Object twórzElement()
	{
		return Tool.getDefGraph().createCompatibleImage(1280, 1024);
	}
	protected void pobierzElementy(int ilość)
	{
		zassane = new BufferedImage[ilość];
		for( int i = 0; i < zassane.length; i++ )
			zassane[i] = (BufferedImage) twórzElement();
	}
	public int pobraneElementy() { return zassane.length; }
	private BufferedImage[] zassane;
}

class Zdzieracz_char extends Zdzieracz
{
	public Zdzieracz twórzZdzieracza() { return new Zdzieracz_char(); }
	public Object twórzElement() { return null; }
	@Override protected int rozmiarElementu() { return 2; }
	public void pobierzElementy(int ilość)
	{
		zassane = new char[ilość];
		Arrays.fill(zassane, '#');
	}
	public int pobraneElementy() { return zassane.length; }
	private char[] zassane;
}

class Zdzieracz_int extends Zdzieracz
{
	public Zdzieracz twórzZdzieracza() { return new Zdzieracz_int(); }
	public Object twórzElement() { return null; }
	@Override protected int rozmiarElementu() { return 4; }
	public void pobierzElementy(int ilość)
	{
		zassane = new int[ilość];
		Arrays.fill(zassane, 0xEE);
	}
	public int pobraneElementy() { return zassane.length; }
	private int[] zassane;
}

class Zdzieracz_String extends Zdzieracz
{
	public Zdzieracz twórzZdzieracza() { return new Zdzieracz_char(); }
	public Object twórzElement()
	{	//w założeniu ciąg o stałejdługości, ale unikalnej zawartości
		return String.format("%016X", licznik++);
	}
	protected void pobierzElementy(int ilość)
	{
		zassane = new String[ilość];
		for( int i = 0; i < zassane.length; i++ )
			zassane[i] = (String) twórzElement();
	}
	public int pobraneElementy() { return zassane.length; }
	private String[] zassane;
	private static long licznik;
}

class Tool //narzędziowa - olać
{
	/**
	 * Przekazuje domyślną konfigurację graficzną domyślnego
	 * monitora w lokalnym środowisku graficznym
	 * @return getDefaultConfiguration dla lokalnego środowiska
	 */
	public static GraphicsConfiguration getDefGraph()
	{
		return GraphicsEnvironment.getLocalGraphicsEnvironment()
			.getDefaultScreenDevice().getDefaultConfiguration();
	}
	
	/**
	 * Zawsze odczekuje czas wymagany do uśpienia.
	 * Uśpienie wznawia się aż do odczekania wymaganego czasu.
	 * @param uśpij wymagany czas uśpienia wątku
	 */
	public static void sleep(int uśpienie)
	{
		if(SwingUtilities.isEventDispatchThread())
		{
			System.err.println(
				"\n***\nSleep invoked from dispatch thread!\n***\n");
			System.exit(15);
		}
		long momentStart = System.currentTimeMillis();
		int odespano = 0;
		do
		{	//assert odespano < wait;
			try { Thread.sleep( uśpienie - odespano ); }
			catch( InterruptedException ex ) {}
			odespano += (int)(System.currentTimeMillis() - momentStart);
		}
		while(odespano < uśpienie); //kończy gdy zrobi swoje
	}
	
	/**
	 * Przyjmuje 64-bitowe dane jako rozmiar w bajtach, produkuje napis
	 * określający rozmiar pamięci w odpowiedniej jednostce:
	 * B, KB, MB, GB, TB, PB lub EB
	 * @param pamięć 64-bitowa ilość bajtów pamięci
	 * @return Ciąg reprezentujący rozmiar pamięci w najbardziej
	 * odpowiedniej jednostce. Jeżeli pamięć nie jest potęgą 2, to
	 * wyrażony jest w formie ułamka z dwoma miejscami po przecinku
	 * oraz najbliższą jednostka w której ułamek jest większy od 1.
	 */
	public static String memorySize(long pamięć)
	{
		final int rozmiarJedn = 10; //rozmiar rzędu Jednostki w bitach
		final char[] nazwaJedn = { ' ', 'K', 'M', 'G', 'T', 'P', 'E' };
		//dzielimy przez 1024 tak długo dopóki bytes będzie większe od 1024
		//lub skala nie dojdzie do końca
		int rządJedn = 0; //wielokrotność 2^rozmiarJedn
		while((pamięć >> rządJedn * rozmiarJedn) >= (1 << rozmiarJedn))
			if(rządJedn <= nazwaJedn.length)
				++rządJedn;
		StringBuilder wynik = new StringBuilder();
		if(rządJedn == 0)
			wynik.append(pamięć);
		else //jeżeli jest to wielokrotność 1024
		{	//liczba bitów dla rzędu jednostki
			int skalaJednostki = rządJedn * rozmiarJedn;
			//maska z ustawionej liczby dolnych bitów wg skali jednostki
			long niższeJednostki = (-1L >>> (Long.SIZE - skalaJednostki));
			//czy całkowita wielokrotność binarna
			if((pamięć & niższeJednostki) == 0)
				wynik.append(pamięć >> skalaJednostki); //wartość dokładna
			else //wartość przybliżona ułamkiem
				wynik.append(String.format("%.2f",
					(pamięć >> (rządJedn - 1) * rozmiarJedn) / 1024d ));
		}
		wynik.append(nazwaJedn[0]); //separator
		if(rządJedn != 0) //tylko dla wyższych wielkości niż bajty
			wynik.append(nazwaJedn[rządJedn]); //dodaje wielkość jednostki
		return wynik.append('B').toString();
	}
}

Krótko opisując Wyciskacz tworzy tablicę obiektów (Zdzieraczy), które w swoim ciele zawierają tablicę konretnego typu elementów. Elementy mogą być obiektami, albo typami prostymi (int, byte itp.). Program sprawdza ile ma w danym momencie wolnej pamięci i przydziela 1/x jej części przydzielając odpowiednią ilość elementów Zdzieracza w wewnętrznej tablicy, której rozmiary łatwo policzyć (rozmiary Zdzieracza i elementu są sprawdzane w konstruktorze). Odśmiecarka oczywiście może znajdować kolejną pamięć, ale w końcu przestaje się to jej udawać. Program kończy działania po 10 bezkutecznych próbach przydziału kolejnej porcji, po wyczerpaniu pojemności tablicy Zdzieraczy lub po zejściu wolnej pamięci poniżej zadeklarowanego progu (np. 1 KB).

ps. Właśnie w Netbeans zauważyłem, że chyba można w projekcie aplikacji/apletu dodać opcje przydziału pamięci. To być może rozwiązuje mój problem, chociaż nadal mnie denerwuje, że nie jest to dynamicznie zależne od pamięci RAM w systemie.

Aczkolwiek wszelkie uwagi i wiedza na ten temat byłaby mi przydatna. :|

0

Uruchom test z opcjami -Xmx i -Xms szczegóły w googlu. Generalnie bedziesz sterował ilością dostępnej pamięci.

0

Co do tych stringow, to przelecialem wzrokiem twoj program i chyba tworzysz stringi poprzez formatowanie na system szesnastkowy kolejnych wartosci licznik. Czyli w duzych ilosciach pewne stringe beda powtarzane. Przykladowo, kolejne wartosci F, FF, FFF, FFFF itd - tak na prawde w javie to bedzie jeden String, rowny najdluzszemu strongwi z takim wzorcem, dajmy na to FFFFFFFF (8 efów), tylko indeks startu i konca bedzie wskazywac na rozne miejsca w tym stringu. To zdecydowanie zmniejszy prawdziwa zajetosc stringow w pamieci, tak dziala i jest pomyslany javowy pool strongow. Jesil sie myle prosze mnie poporawic, rowniez jesli zle przeanalizowalem kod programu.

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