# TreeSet benötigt zu viel Speicher



## soulspirit (30. Jul 2007)

Hi,

ich würde gerne auf meiner Postgresdatenbank ein paar Performancetests durchführen. 
Deshalb hab ich erstmal ca 300000 Datensätze generiert. Die habe ich dann in ein csv-Format geparst, da Postgres von csv-Files direkt einen Bulkload in eine Tabelle machen kann.
Ein Problem ergibt sich jedoch, wenn ich anstatt der 300000 Datensätze nun 3 Millionen Datensätze generieren will:

```
public static void main(String[] args) {



        Random rand = new Random();
        TreeSet<Integer> idxTree = new TreeSet<Integer>();
        TreeSet<String> nickTree = new TreeSet<String>();
        //ArrayList<Integer> idxArray = new ArrayList<Integer>();
        //ArrayList<String>  nickArray = new ArrayList<String>();
        Integer index = null;
        StringBuilder sb = new StringBuilder();
        String newLine = System.getProperty("line.separator");
        String nickname = null;

        try {
            FileOutputStream fout = new FileOutputStream("records.csv");
            PrintStream ps = new PrintStream(fout);

            for (int j = 0; j < 100; j ++){
                for (int i = 0; i < 30000; i++){
                    index = rand.nextInt(1000000);
                    if (!idxTree.contains(index)){
                        .... ein paar StringBuilder.append() 's
                        //index
                        idxTree.add(index);
                        .... ein paar StringBuilder.append() 's
                        while(nickTree.contains(nickname = Main.genWord(rand)));
                        nickTree.add(nickname);
                        .... ein paar StringBuilder.append() 's
                        
                    }
                }
                System.gc();  //versuchsweise GarbageCollector anstupsen
                String temp = sb.toString();  //HIER java.lang.OutOfMemoryError!
                ps.print(temp);
                ps.flush();
                fout.flush();

                System.out.println(new Integer((j+1)).toString() + "Percent...");
                System.out.println("current length of StringBuilder: " + sb.length());
                sb = new StringBuilder();
                
            }
            fout.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
    }
```
Der Fehler tritt deshalb auf, weil die Treesets einfach zu viele Knoten haben. Bei ca 400000 Knoten wirds der VM zu viel. Gibts ne elegantere Methode sowas zu handeln? Kann ja schlecht 10 mal so viel HeapSpace reservieren (falls das überhaupt geht), außerdem ist das Ganze sogar mit TreeSets ab ca 14% nicht mehr wirklich performant, was vielleicht auch an meienm Rechner liegen mag. Ich brauch die Treesets(oder was vergleichbares) jedoch, da ich sonst Doppelte PrimaryKeys habe.

//edit:
auch ohne den treesets wirds der VM anscheinend zu viel. Der Fehler tritt aber immer noch an der selben Stelle auf:


> Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
> at java.lang.String.<init>(String.java:208)
> at java.lang.StringBuilder.toString(StringBuilder.java:431)
> at Main.main(Main.java:127)
> ...


----------



## Wildcard (30. Jul 2007)

Überleg dir doch bitte mal was du hier tust:

```
for (int j = 0; j < 100; j ++){
                for (int i = 0; i < 30000; i++){
                    index = rand.nextInt(1000000);
                    if (!idxTree.contains(index)){
                        .... ein paar StringBuilder.append() 's
```
Hast du die leiseste Ahnung wie viele Arrays da allokiert und kopiert müssen und wie groß das letzte Array bei ein paar Millionen Einträgen später sein wird?


----------



## soulspirit (30. Jul 2007)

darum mach ich am ende der inneren for-schleife ja ein sb = new StringBuilder(), damit die Datenmenge nicht explodiert.. Aber anscheinend funktioniert das nicht so wie ich das will..

und 30000 schafft die VM locker. Nur wird der Speicher anscheinend nicht mehr freigegeben.


----------



## Wildcard (30. Jul 2007)

soulspirit hat gesagt.:
			
		

> und 30000 schafft die VM locker. Nur wird der Speicher anscheinend nicht mehr freigegeben.


Das ist aber auch von der Größe der Strings abhängig.
Weiterhin solltest du unbedingt den StringBuilder auf einen sinnvollen Wert initialisieren um die Anzahl der ArrayCopy Operationen zu minimieren. Für was brauchst du den StringBuilder überhaupt? Du willst doch nur was in den Stream schreiben.


----------



## Marco13 (30. Jul 2007)

Du kannst den Speicher, der für die JVM zur Verfügung steht, angeben, z.B. 1000 MB:
java -Xmx*1000m* MeinProg

Trotzdem hat Wildcard recht: Wenn du die Daten nur in einen Stream schiebst, besteht eigentlich kein Grund, sie in einem gigantischen(!) String zwischenzuspeichern.


----------



## soulspirit (31. Jul 2007)

Der Grund für den String war, dass ich anfangs dachte es würde vielleicht performancemäßig was rausschauen, wenn ich alles mit einem schnellem StringBuilder zusammenflicke und dann in einem einzigen Schreibvorgang eine große Datenmenge schreibe. Hat sich aber als ein großer Trugschluss erwiesen  :? 

Inzwischen habe ich ein befriedigendes Ergebnis erhalten:
- Direktes schreiben in den Stream
- In die Treesets immer nur Teile der Indexe gespeichert, indem ich z.b. für usernamen immer 120000 Datensätze mit gleichem Präfix speichere und anschließend das Treeset leere und das Präfix ändere.

So schaffe ich in ca 5 Minuten alle 3Mio Datensätze. Vielleicht wäre noch etwas rauszuholen, für meine Zwecke genügt das jedoch.

Vielen Dank für die hilfreichen Tips  :toll:


----------

