2016-05-14 5 views
1

Я создал ArrayList из 1 миллиона объектов MyItem, а потребляемая память была 106mb (отмечена в диспетчере задач). Но после добавления того же списка в еще два списка с помощью метода addAll() он занимает 259 мб. Мой вопрос заключается в том, что я добавил только ссылки на список, после этого не создаются новые объекты. 1 миллион. Почему увеличение потребления памяти происходит, хотя LinkedList был использован (поскольку он не требует смежных блоков памяти, чтобы не было перераспределения)?выделение памяти в коллекции 1 млн ссылок в java

Как достичь этого эффективно? Данные проходят через различные списки в моей программе и потребляют более 1 ГБ памяти. Аналогичный сценарий представлен выше.

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

Базы ArrayList, обозначенные его именем в массиве. Если массив становится слишком маленьким, создается новый массив, удваивается размер и ссылки копируются в новый массив. Поскольку у Java есть собственное управление памятью, память первого массива будет выпущена только JVM internal. – Robert

ответ

3

LinkedList является doubly-linked list, поэтому элементы в го e представлены узлами, и каждый узел содержит 3 ссылки.

От 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; 
    } 
} 

Поскольку вы используете много памяти, вы не можете использовать сжатую ООП, поэтому ссылка может быть 64 битами, то есть 8 байт каждая.

С заголовком объекта размером 16 байт + 8 байтов на ссылку, узел занимает 40 байт. С 1 миллионом элементов, это будет 40 Мб.

Два списка - 80 Мбайт, а затем помните, что память Java сегментирована в пулы и объекты (узлы) перемещается, а потребление памяти дополнительно 153 МБ теперь кажется правильным.

Примечание: Arraylist будет использовать только 8 байтов на элемент, а не 40 байт, и если вы предварительно выделите массив подстановки, который вы можете сделать, так как вы знаете размер, вы сохранили бы много памяти таким образом.

+0

_Используя много памяти, вы не можете использовать сжатый OOP_, по умолчанию UseCompressedOops включен на 64-разрядной JVM, если куча меньше 32 ГБ, я был бы удивлен, если это не здесь, так как это просто простой тест, вы не согласны? –

3

В любое время вы звоните LinkedList.addAll за сценой это создаст LinkedList.Node для каждого добавленного элемента таким образом здесь вы создали 3 миллионов таких узлов, который не является свободным, на самом деле:

  1. Этот объект имеет 3 ссылки, зная, что размер ссылки равен 4 bytes по 32-bit JVM и 64-bit JVM с включенным UseCompressedOops (-XX: + UseCompressedOops), который по умолчанию имеет значения с кучками менее 32 GB в Java 7 и выше и 8 bytes по адресу 64-bit JVM с UseCompressedOops отключен (-XX: -UseCompressedOops). Итак, в соответствии с вашей конфигурацией он дает 12 байтов или 24 байта.
  2. Затем мы добавляем размер полей заголовка, который является 8 bytes, на 32-bit JVM и 16 bytes на 64-bit JVM. Итак, в соответствии с вашей конфигурацией он дает 8 байтов или 16 байтов.

Итак, если суммировать он принимает:

  1. 20 байт для каждого экземпляра на 32-bit JVM
  2. 28 байт для каждого экземпляра на 64-bit JVM с UseCompressedOops включен
  3. 40 байт на экземпляр на 64-bit JVM с UseCompressedOops отключен

Как вы вызываете 3 раза addAll 1 млн объектов на LinkedList, это дает

  1. 60 Mo на 32-bit JVM
  2. 84 Mo на 64-bit JVM с UseCompressedOops включен
  3. 120 Mo по телефону 64-bit JVM с UseCompressedOops инвалидов

Остальное, вероятно, объекты еще не собраны сборщиком мусора, вы должны попытаться вызвать System.gc() после загрузки вашего ArrayList, чтобы получить реальный размер и сделать то же самое после загрузки вашего LinkedList.

Если вы хотите, чтобы получить размер данного объекта, вы можете использовать SizeOf.

Если вы используете 64-bit JVM и вы хотите знать, если UseCompressedOops включена, то достаточно просто запустить команду Java в терминале только -X вариантов и добавляет -XX:+PrintFlagsFinal | grep UseCompressedOops так, например, если моя команда java -Xms4g -Xmx4g -XX:MaxPermSize=4g -cp <something> <my-class>, запуск java -Xms4g -Xmx4g -XX:MaxPermSize=4g -XX:+PrintFlagsFinal | grep UseCompressedOops, в начале Вывод должен выглядеть следующим образом:

 bool UseCompressedOops      := true   {lp64_product} 
    ... 

В этом случае флаг UseCompressedOops включена

+0

Я думал, что LinkedList.Node будет присвоен ссылкой! Если нет, то как я могу работать без выделения большего количества узлов (спасибо за ваш ответ! –

+0

Это то, что он делает, но создает экземпляр LinkedList.Node для каждого добавленного элемента, чтобы сохранить предыдущий и следующий узел –

+0

, что такое точная команда java что вы используете для своего теста? –

Смежные вопросы