GC Warning: Repeated allocation of very large block

Status
Nicht offen für weitere Antworten.
G

Guest

Gast
Mit erstaunen musste ich gerade feststellen das die Boardsuche z.B. auf "Repeated allocation" keinen einzigen Treffer ergab. bin ich etwa der einzige Depp der es geschafft hat diesen Fehler zu produzieren? :shock: :oops:


Ich habe ein Programm das Daten aus relativ großen Dateien(50-200MB) einliest und dabei >100000 Objekte in einem Vector anlegt. Gelesen wird jeder Datensatz byteweise in einer Schleife. Jedes der ca. 100000 Objekte wird in der Schleife zuerst mit "new" als temporäres Objekt allokiert, die Daten werden gelesen und per "Set"-Methoden der Unterobjekte(die wiederum im Konstruktor mit "new" allokiert werden, besitzen je Unterobjekt ca. 10 Attribute und dafür je 10 Getter und Setter, sind also nur reine Datenklassen) geschrieben. Nachdem so ein temp Object mit allen daten aus dem jeweiligen Datensatz befüllt wurde, schiebe ich es mit "add(temp)" in den Vector.

Nun, das scheint so wohl nicht sehr elegant zu sein. Da ich bis jetzt nur C++ programmiert habe vermisse ich ein explizites delete(oder würde ich damit das Objekt im Vector auch zerstören da identisch). Irgendwie steig ich da nicht ganz durch. Code ist leider recht umfangreich, deshalb vermeid ich es mal den hier ausfürhlich zu posten(hoffe meine Umschreibung reicht euch, wenn nicht bitte gezielt nachfragen).




Das schmeisst mir die Eclipse Console um die Ohren:
....
....
....
GC Warning: Repeated allocation of very large block (appr. size 655360):
May lead to memory leak and poor performance.
GC Warning: Repeated allocation of very large block (appr. size 659456):
May lead to memory leak and poor performance.
GC Warning: Repeated allocation of very large block (appr. size 663552):
May lead to memory leak and poor performance.
...
...
...
GC Warning: Repeated allocation of very large block (appr. size 37752832):
May lead to memory leak and poor performance.

und in der Tat: Es dauert scheisslange bis die Operation eine Datei ins Programm einzulesen abgeschlossen ist. Mit den Daten zu arbeiten geht dann hingegen wesentlich flotter...

Was mach ich alles falsch? hab schon probiert soviel "new" wie möglich zu vermeiden, aber an einigen Stellen brauch ich es doch und die 100000 Schleifendurchläufe kann ich auch nicht vermeiden. So langsam verzweifel ich dran. Das Schlimme ist, neue Sachen auszuprobieren macht einen total fertig weil es ja immer so lang dauert um ne Kleinigkeit zu testen (Programm abschiessen wenn man keinen Bock mehr allein dauert ne halbe Minute...) Hoffe irgendwer kann mir helfen :cry:
 

Wildcard

Top Contributor
Wirf den Vector raus und nimm eine LinkedList.
Weiterhin solltest du bei dieser Menge an Daten unbedingt auf java.nio umsteigen und vor allem nicht byteweise! lesen.
 

pocketom

Bekanntes Mitglied
Alles klar, auf LinkedList ist schnell umgebaut, das mach ich mal sofort! Anschliessend beschäftige ich mich mal mit java.nio. Nur wie ich das byteweise lesen vermeiden könnte ist mir grad nicht so ganz klar, in der Datei sind verschiedene Datentypen drin.

ich mach das ungefähr so:

Code:
byte[] bytes;
ByteBuffer buf;

			
for(int i=0; i< fileheader.getNumber_of_reads();i++)
{
    bytes = new byte[16];

   if(fin.read(bytes, 0, 16) != -1)
       buf = ByteBuffer.wrap(bytes);
   else
      return false;
				
    var1	= buf.getShort();
    var2	= buf.getInt();
    var3        = buf.getFloat();
}

Wurde mir in irgendeiner anderen Quelle als schnelle Methode empfohlen... Geht besser?
 

Wildcard

Top Contributor
Map die Datei in Portionen von einigen MB mit nio in den Speicher und lies die Daten aus dem ByteBuffer.
 

pocketom

Bekanntes Mitglied
Du meinst also nicht wie in meinem Beispiel 16 bytes, sonder z.B. 4096? Problem ist, die einzelnen Datensätze haben alle eine unterschiedliche Länge, denn es sind leider auch Arrays mit jeweils unterschiedlicher Länge in der Datei drin, d.h. es werden nur in meinem Beispiel 16 byte beutzt, in der Praxis sind es mehrere Arrays, dazwischen auch immer paar ints, shorts, floats. Ich hangel mich da im Moment so durch. Wenn ich also z.B. immer 4096 in den Buffer lese dann zerhack ich ja irgendwo was :shock:

oder meintest du das irgendwie anders?
 

Wildcard

Top Contributor
pocketom hat gesagt.:
Du meinst also nicht wie in meinem Beispiel 16 bytes, sonder z.B. 4096?
...
Wenn ich also z.B. immer 4096 in den Buffer lese dann zerhack ich ja irgendwo was :shock:
Nein, ich rede von zB 4 000 000.
Wo ist der Unterschied ob du 16 bytes oder mehr ausliest?
 
G

Guest

Gast
Nun, die Datensätze aus denen jeweils ein Objekt und somit ein Schleifendurchlauf resultiert sind unterschiedlich lang. Nach den 16 byte kommt ein Array variabler Länge, wie lange es ist lese ich davor in den zugehörigen Headerinfos die in den vorangegangenen 16 Bytes enthalten sind aus um zu wissen wieviele bytes ich als nächstes brauche (i.d.R.100-300). Das Spielchen geht dann noch 4 mal so weiter. Ich müsste also zuerstmal nur die Header der kommenden sagen wir 100 Datensätze auslesen um die Länge eines größeren Pakets bestimmen zu können. Damit habe ich aber doch nichts gewonnen, erspart mir ja eben genau das lesen der unzähligen 16 byte Blöcke nicht.

Habs gerade ausprobiert, und einfach mal den Buffer mit 4 MB befüllt. Das geht dann genau so lange gut bis der Buffer leer geht, der an dieser Stelle behandelte Datensatz kann nicht vollständig gelesen werden und das Programm schmiert ab. Wie ich quasi an beliebiger Stelle der Schleife Daten in den Buffer nachfülle weiss ich nicht. Das einzige was mir einfällt ist mit ein paar if-Abfragen am Anfang des Schleifendurchlaufs zu chekcen wieviel noch im Buffer drin ist, wieviel benötigt würde und wieviel noch aus der Datei zu holen wäre. Ob das der Performance so gut tut? Mit den Unterobjekten die wieder so ähnlich aufgebaut sind produzier ich dann halt schnell ein paar Millionen If-Abfragen :autsch:

Vielleicht versteh ich aber einfach immer noch nicht so ganz wie du das meinst.... Kannst du mir vielleicht einen kleinen Code-Schnipsel zum Verständnis posten?


Hier nochmal das Schema meines Code, also jetzt das mit der variablen Länge....

Code:
byte[] bytes;
ByteBuffer buf;

         
for(int i=0; i< fileheader.getNumber_of_reads();i++)
{
    
    // fetch entitys header info
    bytes = new byte[16];

   if(fin.read(bytes, 0, 16) != -1)
       buf = ByteBuffer.wrap(bytes);
   else
      return false;
            
    array_1_length   = buf.getInt();
    array_2_length   = buf.getInt();
    array_3_length   = buf.getInt();

    var1             = buf.getInt();
    var2              = buf.getInt();
    var3              = buf.getFloat();
     ...
     ...
   
   // fetch entitys data
   bytes = new byte[array_1_length];

    if(fin.read(bytes, 0, arraylength) != -1)
       buf = ByteBuffer.wrap(bytes);
   else
      return false;   

   for(int i=0;i<array_1_length;i++)
        targetarray[i] = buf.get();
    ...
    ...
}
 

Wildcard

Top Contributor
Einfach die FileChannel#read Methode benutzen.
Wenn dir der Buffer ausgeht (du musst dir merken wie viel schon gelesen wurde) lädst du nach.
Bei dieser Konstellation von unbekannten Blocklängen wird's an den 'Kanten' vermutlich etwas hässlich, solltest du aber hinbekommen.
Ansonsten:
Hat die Verwendung der LinkedList Vorteile oder Nachteile gebracht? Ist die Warnung noch vorhanden?
 
G

Guest

Gast
Die LinkedList hats auf jeden Fall mächtig gebracht. Die Warnung ist weg, mein Systemmonitor zeigt mir an das nun aus 100 MB Daten "nur" noch ca. 400 MB im Speicher entstehen. Einlesen einer solchen Datei dauert aber immer noch ziemlich lange, bei kleinerern Dateien merkt man komischerweise überhaupt keinen Geschwindigkeitsvorteil, deshalb werd ich auf jeden Fall versuchen da noch was rauszuholen. Denk aber mal wenn man so viele Objekte generiert gehts halt auch nicht so schnell oder? Was meinst du, wie lang sollte es maximal dauern 100000 Objekte von meines Typs so in etwa einzulesen, damit ich weis wonach ich mich so ungefähr richten kann?
 

Wildcard

Top Contributor
Anonymous hat gesagt.:
Die LinkedList hats auf jeden Fall mächtig gebracht. Die Warnung ist weg, mein Systemmonitor zeigt mir an das nun aus 100 MB Daten "nur" noch ca. 400 MB im Speicher entstehen. Einlesen einer solchen Datei dauert aber immer noch ziemlich lange, bei kleinerern Dateien merkt man komischerweise überhaupt keinen Geschwindigkeitsvorteil, deshalb werd ich auf jeden Fall versuchen da noch was rauszuholen.
Bei kleinen Dateien kann das auch nichts bringen.
Dein Problem war, dass eine ArrayList Array basiert ist.
Du packst mehr und mehr dazu, das Array ist zu klein, ein doppelt so großes wird erstellt und alle Daten werden kopiert.
Das du füher oder später einen gigantischen Overhead bekommst ist nicht verwunderlich.
Im Hinterkopf behalten, die richtige Wahl der Collection kann entscheidend sein :wink:
 
G

Guest

Gast
Ah ok, alles kalr. In zukunft also immer LinkedList, es sei denn ich hab weniger Daten und muss häufig gezielt auf Objekte in der Mitte zugreifen.

Sodele, das mit den FileChannels haut leider auch nicht so ganz hin. Ich habs mir jetzt mal einfach gemacht und achte garnicht darauf häppchenweise einzulesen. Ich probiers grad nur mit ner kleinen Datei und les die einfach komplett in den buffer:

Code:
			int size = (int)file.length();
			
			FileChannel fin = new FileInputStream(file).getChannel();
			MappedByteBuffer buf = fin.map(FileChannel.MapMode.READ_ONLY, 0, size);
			fin.close();

Vorgehensweise danach wie gehabt. Komischerweise liest er nur den ersten Datensatz, in der Hälfte des zweiten terminiert es dann mit einer BufferUnderFlowException. Aber ich hab das File (hat 63 Datensätze) komplett in den Buffer geschoben ???:L
 

pocketom

Bekanntes Mitglied
Hab mal ein paar Debugausgaben eingebaut. Irgendwo wird der buffer scheinbar zuschnell aufgefuttert. Prinzipiell sollte das aber jetzt die richtige Vorgehensweise sein oder?
 

pocketom

Bekanntes Mitglied
Fehlerchen gefunden, habe vergessen das ich bevor die Daten kommen ja schon den Fileheader ausgelesen habe. Demensprechend so viele bytes müssen übersprungen werden (Fileheader auslesen hab ich in ner eigenen Methode).

Funktioniert also wenn ich die Files komplett lese ganz gut. Häppchenweise einlesen bau ich halt zwecks den großen Files jetzt noch ein(wie find ich die optimale Häppchengröße raus, naja TryandError würd ich mal sagen).
Leider aber komischerweise immernoch kein Geschwindigkeitsvorteil spürbar. Die Einlesefunktion ist aber wesentlich kompakter als vorher. Überhaupt keine If-Abfrage mehr drin, kein allokieren von bytearrays azwischen, usw. Als Testdatei habe ich eine ~5 MB Datei mit 6000 Datensätzen genommen, also in etwa die 4 MB die du vorgeschlagen hast. Einlesen dauert ziemlich genau 5 Sekunden. 100 MB also ca. 100 Sek. :bahnhof:

Fällt dir noch irgendwas ein um da zu tunen oder ist das Ende der Fahnenstange hier schon erreicht?
 

Ark

Top Contributor
Benötigst du wirklich(!) alle Datensätze auf einmal? Falls(!) dem so ist: puffere nicht die konkreten Daten, sondern lege einen Index (quasi Sprungtabelle) an und nutze diesen. Wenn der Index zu groß für den Arbeitsspeicher wird, kannst du ihn evtl. auch temporär auf die Festplatte schreiben.

Ark
 

pocketom

Bekanntes Mitglied
So mit Sprungindex hab ich auch schon überlegt, das wäre easy, aber die Datensätze sind selbst ja recht klein und zahlenmäßig sehr viele (>100.000). Wegen den Arrays variabler Länge muss ich eh einmal durch um die Tabelle anzulegen, da hau ich lieber gleich alles einmal in den Hauptspeicher(4GB verfügbar, reicht IMO, bis auf 16GB aufrüsten wäre auch kein Thema). Danach wird i.d.R. über alle Datensätze gearbeitet (Statistik, diverses Processing, Sortierung, usw.




Code:
public boolean test(File file)
{
                try
		{
		    // Establish file channel
			// and skip amount of bytes used for 
			// fileheader information when mapping bytes into buffer
			
			FileInputStream fin = new FileInputStream(file);
			FileChannel fc = fin.getChannel();
			MappedByteBuffer buf = fc.map(FileChannel.MapMode.READ_ONLY, fileheader.getHeader_length(), (int)file.length());
			
			// iterate over reads
			for(int i=0; i< fileheader.getNumber_of_reads();i++)
			{
			    // a single read's header info
			    var1             = buf.getShort);
			    var2             = buf.getInt();
			    var3             = buf.getFloat();			
                            ....
			
			    // a single read's data array's lengths
			    array_1_length   = buf.getInt();
			    array_2_length   = buf.getInt();
			    array_3_length   = buf.getInt();
                            ....
			    
			    // temp object
			    MyRead temp = new Myread();
	
			    // fetch data into destination arrays (occuring 4 times)
			    for(int j=0;j<array_1_length;j++)
			    	temp.getData().setArray_1_Value(buf.get(), j);
				...
				...		
				
				// add to collection
				myarraylist.add(temp);			
			}
			return true;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return false;
		}
}

Ich vermute mal das die for-Schleifen nicht so der Knaller sind. Irgendwie werd ich die aber ned (vernünftig) los.


Ich habs mal so probiert, ich muss nämlich unter anderem eine Folge von chars einlesen und einen String draus machen:

Code:
				String temp_name ="";				
				byte temp[] = new byte[temp_name_length];
				buf.get(temp);				
				temp_name = temp.toString();

liefert leider voll den komischen Salat, der String den ich haben will isses nicht...

**********************************************************************************
EDIT: Sorry, das war dämlich....

für alle die sich das selbe fragen, so gehts:

Code:
                               String temp_name = new String(buf.get(new byte[temp_name_length]));

Jetzt hab ich aber wieder zwei "new" drin, sollte man ja auch vermeiden wos nur geht oder?
 

Wildcard

Top Contributor
>100000 neue Objekte ist nunmal sehr teuer.
Wenn du keine Möglichkeit siehst das einzuschränken (Lightweight Pattern oder ähnliches), musst du wohl damit leben.
 

Ark

Top Contributor
Eventuell könntest du noch versuchen, nicht für jeden Datensatz ein richtiges Objekt zu erstellen, sondern lediglich eine Art Hülle anzulegen, also ein Objekt, das „zufälligerweise“ gerade die benötigten Daten enthält, aber ansonsten immer wieder recycled wird. (Ich hoffe, ich bin verständlich. o_o)

Ark
 

pocketom

Bekanntes Mitglied
Puh, jetzt wirds wirklich schwierig oder? Habe mal ein bischen gegoogelt, das mit dem Lightweight Pattern versteh ich nicht so ganz (ist damit das Flyweight pattern nach GoF gemeint). Auch was du Ark meinst mit der leeren Hülle die zufällig die benötigten Daten enthält *MEGABAHNHOF* :shock: Kannst du mir das erklären, klingt sehr interessant.

Mit Design Patterns hab ich noch nie wirklichbewusst gearbeitet (unterbewusst verwendet man halt glaub ich schon vieles nach diesen Mustern). Hatte das zwar mal in UML an der FH, da haben wir uns ein paar GoF patterns angesehen, aber das war nur so ne Mini-Vorlesung...

---
Prinzipiell sollte ich aber schon die for-Schleifen loswerden oder machen die Methoden(siehe z.B. die letzte Codezeile oben mit dem String) mit denen ichs ersetzen kann im Prinzip auch nichts anderes?
 

Wildcard

Top Contributor
Ark hat gesagt.:
Eventuell könntest du noch versuchen, nicht für jeden Datensatz ein richtiges Objekt zu erstellen, sondern lediglich eine Art Hülle anzulegen, also ein Objekt, das „zufälligerweise“ gerade die benötigten Daten enthält, aber ansonsten immer wieder recycled wird. (Ich hoffe, ich bin verständlich. o_o)

Ark
Siehe oben, Lightweight Pattern :autsch:

Habe mal ein bischen gegoogelt, das mit dem Lightweight Pattern versteh ich nicht so ganz (ist damit das Flyweight pattern nach GoF gemeint).
Ja, genau das.
 

pocketom

Bekanntes Mitglied
Ok, danke. Bringt leider nix weil die Daten in jedem Datensatz quasi komplett anders sind, Redundanzen gibts gleich 0.
 

pocketom

Bekanntes Mitglied
Also nochmal vielen Dank, das ganze flitzt mittlerweile so dermaßen, das hätte ich echt nie gedacht. Mir ist es gelungen sämtliche for-Schleifen zu eliminieren und viele "new"s zu vermeiden. Das brachte neben der Verwendung der LinkedList statt Vector (ca. 3 Mal schneller, keine Memory Leaks mehr) nochmal eine Beschleunigung um ca. das 10-fache!!! Eine 120 MB Datei mit ca. 170 000 Objekten lese ich nun in ca 15 Sekunden ein, der gesamte Objektbaum braucht im RAM dabei lediglich ca. 250 MB. Danach hat man einen sauschnellen Zugriff, z.B. um für Statistiken einmal komplett drüberzulaufen brauche ich keine 5 Sekunden mehr. Es hat sich also gelohnt!

Eine letzte Frage hätte ich aber noch:

Einer meiner beiden Prozessorkerne bleibt "kalt"! Wie bekomme ich den noch zusätzlich aktiviert??? (DELL Optiplex 745 Workstation, Intel Core2 Duo E6700 2,66 Ghz, OS: Fedora 6 Linux)
 

pocketom

Bekanntes Mitglied
Hmm, das ist doof, so ziemlich alle Klassen für die das interessant wäre haben schonmal geerbt. kann ja leider nicht wie in C mehrfachvererbung machen :oops:
 

pocketom

Bekanntes Mitglied
implements Runnable hab ich drin, allein schon wegen dem AsyncExec(), aber es bleibt trotzdem ein Kern ungenutzt. Muss ich explizit auch zwei Threads parallel erzeugen wenn ich beide Kerne nutzen will?
 

Wildcard

Top Contributor
Implementieren alleine hat keine Auswirkung.
Du musst explizit Threads starten.
Code:
new Thread(new ClassThatImplementsRunnable()).start();
 

pocketom

Bekanntes Mitglied
Ok, habs so gemacht. Leider wird laut Monitor aber noch immer nur ein Kern verwendet. Zudem habe ich jetzt das Problem das die Mainklasse mir davonläuft, die muss aber warten bis der Thread fertig ist.

so hab ichs gemacht.

Code:
File file = new file(path);
Parser parser = new Parser(file);
new Thread(parser).start();
parser.join();
//now go on...

Dank dem join() sollte sie doch warten?
 

pocketom

Bekanntes Mitglied
Hoplla, sorry. Es muss natürlich heissen parser.join(); Ich habe es so umgebaut das ich mit Threads arbeiten kann, brauche doch das join()? Fehlt dir irgendwas?

Nochmal ein wenig ausführlicher:

Code:
// FILEPARSER
class Parser extends Thread
{
     File file;
     String result;
    
     Parser(File file)
     {
          super();
          this.file = file;
          result = "I'M AN EMPTY STRING";
     }

     public void run()
     {
          // calc result, copy file to string for example...
     }

     public String getResult()
     {
          return result;
     }
}

// SWT GUI
class MyGUIApp
{

     public static void main(String[] args) throws Exception
     {
          ...
          ...
          ...

          FileDialog dialog = new FileDialog (shell, SWT.OPEN);
          filepath = dialog.open();

          File file = new file(filepath);
          Parser parser = new Parser(file);
          new Thread(parser).start();
          parser.join();
          System.out.println(parser.getResult());

          //now go on...
          ...
          ...
          ...

     }
}
 

Wildcard

Top Contributor
Was soll das bringen?
Du hast derzeit einen Thread der die Arbeit macht und einen der nichts zu tun hat.
Du musst den zeitaufwendigen Vorgang parallelisieren um beide Kerne nutzen zu können.
Sowas wird dir nicht auf magische Weise geschenkt :wink:
 

pocketom

Bekanntes Mitglied
Wie ich schon vorher fragte, muss ich explizit auch zwei Threads parallel erzeugen wenn ich beide Kerne nutzen will? D.h., in meinem Fall läuft die ganze Arbeit ja in einer for-Schleife (das ist die Geschichte die >100000 Mal gemacht wird). Mein Ansatz wäre es jetzt also im einen Thread meinetwegen die Schleifenvariable i gerade hochzuzählen (0,2,4,6,8,...) und im anderen Thread alle ungeraden zu betrachten(1,3,5,7,...). Zum Abschluss wenn beide Threads fertig sind schmeiss ich die beiden Teilergebnisse dann zu einem zusammen (beide entstandenen LinkedLists mergen). Vom Ansatz her soweit ok ?!?

Und, wieso funktionert das join() nicht? gibt es einen anderen (effektiven) Weg auf einen Thread zu warten? So baut das Programm im Moment eh nur Mist...
 

Wildcard

Top Contributor
Du brauchst mindestens soviele Threads wie du Kerne ansprechen willst.
In deinem bisherigen Code hast du zwar mindestens 2 Threads, das nützt dir aber nichts, weil einer der beiden nichts zu tun hat.
Du musst dir also eine Möglichkeit einfallen lassen die Arbeit zu teilen (eine Idee hast du ja bereits).
Zu Thread Synchronisation wende dich vertrauensvoll an die Javainsel.
 

pocketom

Bekanntes Mitglied
Ok, alles klar. Das riecht nach einem neuen Großprojekt :D

Da müsst ich schon einige Klassen umschreiben. Hätte man denk ich vorher schon einplanen sollen, naja was solls. Ich denk, fürs erste werd ich mich wohl mit einem kalten Prozessorkern zufrieden geben, das ist mir IMO zu zeitaufwändig.
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben