2016-05-14 3 views
1

Ich erstellte eine ArrayList von 1 Million MyItem Objekte und der Speicherbedarf war 106mb (vom Task-Manager überprüft) Aber nach dem Hinzufügen der gleichen Liste zu zwei weitere Liste durch addAll() -Methode, es dauert 259mb. Meine Frage ist, dass ich nur die Verweise auf Liste hinzugefügt habe, keine neuen Objekte werden danach 1 Million erstellt. Warum nimmt der Speicherverbrauch zu, obwohl LinkedList verwendet wurde (da es keine zusammenhängenden Speicherblöcke erfordert, so dass keine Neuzuweisung vorgenommen wird)?Speicherzuordnung in Sammlung von 1 Million Referenzen in Java

Wie erreichen Sie dies effizient? Die Daten durchlaufen verschiedene Listen in meinem Programm und verbrauchen mehr als 1 GB Arbeitsspeicher. Ein ähnliches Szenario ist oben dargestellt.

public class MyItem{ 
private String s; 
private int id; 
private String search; 

public MyItem(String s, int id) { 
    this.s = s; 
    this.id = id; 
} 

public String getS() { 
    return s; 
} 

public int getId() { 
    return id; 
} 


public String getSearchParameter() { 
    return search; 
} 

public void setSearchParameter(String s) { 
    search = s; 
} 
} 

public class Main{ 
    public static void main(String args[]) { 
     List<MyItem> l = new ArrayList<>(); 
     List<MyItem> list = new LinkedList<>(); 
     List<MyItem> list1 = new LinkedList<>(); 

     for (int i = 0; i < 1000000 ; i++) { 
      MyItem m = new MyItem("hello "+i ,i+1); 
      m.setSearchParameter(m.getS()); 
      l.add(i,m); 
     } 

     list.addAll(l); 

     list1.addAll(l); 
     list1.addAll(list); 

     Scanner s = new Scanner(System.in); 
     s.next();//just not to terminate 
    } 
} 
+0

Die ArrayList-Basen, wie durch ihren Namen in einem Array angegeben. Wenn das Array ein neues Array zu klein wird, wird eine doppelte Größe erstellt und die Referenzen werden in das neue Array kopiert. Da Java über eine eigene Speicherverwaltung verfügt, wird der Speicher des ersten Arrays nur JVM intern freigegeben. – Robert

Antwort

3

LinkedList ein doubly-linked list ist, so Elemente in th Die Liste wird durch Knoten dargestellt, und jeder Knoten enthält 3 Referenzen.

Von Java 8:

private static class Node<E> { 
    E item; 
    Node<E> next; 
    Node<E> prev; 

    Node(Node<E> prev, E element, Node<E> next) { 
     this.item = element; 
     this.next = next; 
     this.prev = prev; 
    } 
} 

Da Sie viel Speicher verwenden, können Sie nicht komprimierte OOP verwenden, so Referenzen 64-Bit sein könnte, das heißt jeweils 8 Byte.

Bei einem Objektkopf von 16 Byte + 8 Byte pro Referenz belegt ein Knoten 40 Byte. Mit 1 Million Elementen wären das 40 Mb.

Zwei Listen sind 80 Mb, und dann daran erinnern, dass Java-Speicher in Pools segmentiert und Objekte (die Knoten) verschoben wird, und Ihr Speicherverbrauch von zusätzlichen 153 Mb scheint jetzt etwa richtig.

Hinweis: Eine Arraylist würde nur 8 Bytes pro Element verwenden, nicht 40 Bytes, und wenn Sie das Backing-Array vorreservieren, was Sie tun können, seit Sie die Größe kennen, würden Sie eine Menge Speicher auf diese Weise speichern.

+0

_ Da Sie viel Speicher verwenden, verwenden Sie möglicherweise nicht komprimierte OOP_, standardmäßig UseCompressedOops ist auf 64-Bit-JVM aktiviert, wenn der Heap weniger als 32 GB ist, würde ich überrascht sein, wenn dies der Fall ist nicht der Fall hier, da es nur ein einfacher Test ist, stimmst du nicht zu? –

3

Immer, wenn Sie LinkedList.addAll hinter der Szene nennen es eine LinkedList.Node für jedes hinzugefügte Element erstellen, damit hier erstellt man 3 Millionen solcher Knoten, die nicht frei ist, in der Tat:

  1. Dieses Objekt hat 3 Referenzen, wissen, dass die Größe einer Referenz ist 4 bytes auf 32-bit JVM und 64-bit JVM mit UseCompressedOops (-XX: + UseCompressedOops) aktiviert, was der Fall ist standardmäßig mit Heaps kleiner als 32 GB in Java 7 und höher und 8 bytes unter 64-bit JVM mit UseCompressedOops deaktiviert (-XX: -UseCompressedOops). Also hier nach Ihrer Konfiguration gibt es 12 Bytes oder 24 Bytes.
  2. Dann fügen wir die Größe der Header-Felder, die auf 32-bit JVM ist und 16 bytes auf 64-bit JVM. Also hier nach Ihrer Konfiguration gibt es 8 Bytes oder 16 Bytes.

Also, wenn wir zusammenfassen, es nimmt:

  1. 20 Bytes pro Instanz auf 32-bit JVM
  2. 28 Bytes pro Instanz auf 64-bit JVM mit UseCompressedOops aktiviert
  3. 40 Byte pro Instanz auf 64-bit JVM mit UseCompressedOops deaktiviert

Wie Sie 3-mal auf einem LinkedListaddAll von 1 Million Objekte nennen, es gibt

  1. 60 Mo auf 32-bit JVM
  2. 84 Mo auf 64-bit JVM mit UseCompressedOops aktiviert
  3. 120 Mo auf 64-bit JVM mit UseCompressedOops Behinderte

Der Rest ist wahrscheinlich die Objekte noch nicht vom Garbage Collector gesammelt, sollten Sie versuchen, System.gc() zu rufen nach dem ArrayList Laden die reale Größe und das gleiche tun zu erhalten, nachdem Ihre LinkedList geladen.

Wenn Sie die Größe eines bestimmten Objekts erhalten möchten, können Sie SizeOf verwenden.

Wenn Sie eine 64-bit JVM verwenden, und Sie wollen wissen, ob UseCompressedOops aktiviert ist, starten Sie einfach Ihre Java-Befehl in einem Terminal mit nur -X Optionen und fügt -XX:+PrintFlagsFinal | grep UseCompressedOops so zum Beispiel, wenn mein Befehl java -Xms4g -Xmx4g -XX:MaxPermSize=4g -cp <something> <my-class> ist, starten Sie java -Xms4g -Xmx4g -XX:MaxPermSize=4g -XX:+PrintFlagsFinal | grep UseCompressedOops, der Beginn der Ausgabe sollte wie folgt aussehen:

 bool UseCompressedOops      := true   {lp64_product} 
    ... 

in diesem Fall wird die Flagge UseCompressedOops aktiviert ist

+0

Ich dachte, LinkedList.Node wird mit der Referenz zugewiesen werden! Wenn nicht, wie kann ich arbeiten, ohne mehr Knoten zuzuweisen (Danke für Ihre Antwort! –

+0

Es ist, was es tut, aber es erstellt eine Instanz von LinkedList.Node für jedes hinzugefügte Elemente, um den vorherigen und nächsten Knoten –

+0

was ist der genaue Java-Befehl –