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. :|