Коллекции Java: что происходит, когда «size» превышает «int»? - PullRequest
8 голосов
/ 23 августа 2010

Я знаю, что коллекции Java очень требовательны к памяти, и я сам провел тест, доказав, что 4 ГБ едва достаточно для хранения нескольких миллионов Integer с в HashSet.

Но что, если у меня «достаточно» памяти? Что будет с Collection.size()?

РЕДАКТИРОВАТЬ: Решено: Collection.size() возвращает Integer.MAX при превышении целочисленного диапазона.
Новый вопрос: как тогда определить «реальное» количество элементов коллекции?

ПРИМЕЧАНИЕ 1: Извините, это, вероятно, вопрос "дай мне погуглить", но я действительно ничего не нашел;)

ПРИМЕЧАНИЕ 2: Насколько я понимаю, каждая целочисленная запись набора: reference + cached_hashcode + boxed_integer_object + real_int_value, верно?

ПРИМЕЧАНИЕ 3. Забавно, даже с JDK7 и «сжатыми указателями», когда JVM использует 2 ГБ реальной памяти, в VisualVM.

отображается только 1,5 ГБ выделенной памяти.

Для тех, кому небезразлично:

Источники испытаний:

import java.util.*;
import java.lang.management.*;

public final class _BoxedValuesInSetMemoryConsumption {
  private final static int MILLION = 1000 * 1000;

  public static void main(String... args) {
    Set<Integer> set = new HashSet<Integer>();

    for (int i = 1;; ++i) {
      if ((i % MILLION) == 0) {
        int milsOfEntries = (i / MILLION);
        long mbytes = ManagementFactory.getMemoryMXBean().
            getHeapMemoryUsage().getUsed() / MILLION;
        int ratio = (int) mbytes / milsOfEntries;
        System.out.println(milsOfEntries + " mil, " + mbytes + " MB used, "
            + " ratio of bytes per entry: " + ratio);
      }

      set.add(i);
    }
  }
}

Параметры исполнения:

Протестировано с 64-разрядной версией JDK7 build 105 под OpenSuse 11.3 x64.

-XX:+UseCompressedOops -Xmx2048m

Результат вывода:

1 mil, 56 MB used,  ratio of bytes per entry: 56
2 mil, 113 MB used,  ratio of bytes per entry: 56
3 mil, 161 MB used,  ratio of bytes per entry: 53
4 mil, 225 MB used,  ratio of bytes per entry: 56
5 mil, 274 MB used,  ratio of bytes per entry: 54
6 mil, 322 MB used,  ratio of bytes per entry: 53
7 mil, 403 MB used,  ratio of bytes per entry: 57
8 mil, 452 MB used,  ratio of bytes per entry: 56
9 mil, 499 MB used,  ratio of bytes per entry: 55
10 mil, 548 MB used,  ratio of bytes per entry: 54
11 mil, 596 MB used,  ratio of bytes per entry: 54
12 mil, 644 MB used,  ratio of bytes per entry: 53
13 mil, 827 MB used,  ratio of bytes per entry: 63
14 mil, 874 MB used,  ratio of bytes per entry: 62
15 mil, 855 MB used,  ratio of bytes per entry: 57
16 mil, 902 MB used,  ratio of bytes per entry: 56
17 mil, 951 MB used,  ratio of bytes per entry: 55
18 mil, 999 MB used,  ratio of bytes per entry: 55
19 mil, 1047 MB used,  ratio of bytes per entry: 55
20 mil, 1096 MB used,  ratio of bytes per entry: 54
21 mil, 1143 MB used,  ratio of bytes per entry: 54
22 mil, 1191 MB used,  ratio of bytes per entry: 54
23 mil, 1239 MB used,  ratio of bytes per entry: 53
24 mil, 1288 MB used,  ratio of bytes per entry: 53
25 mil, 1337 MB used,  ratio of bytes per entry: 53
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

В конце было использовано около 2 ГБ реальной памяти вместо отображаемых 1,3 ГБ, поэтому потребление для каждой записи даже больше , чем 53 байта.

Ответы [ 4 ]

14 голосов
/ 23 августа 2010

Я знаю, что коллекции Java очень жаждущий памяти, и сам сделал тест, доказывая, что 4 ГБ едва достаточно для хранить несколько миллионов Integers в HashSet.

Java Heap! = Системная память. Размер кучи Java по умолчанию составляет всего 128 МБ. Обратите внимание, что это также отличается от памяти, используемой JVM.

По вашему вопросу: из документов,

public int size()

Возвращает количество элементов в этом коллекция. Если эта коллекция содержит более Integer.MAX_VALUE элементы, возвращает Integer.MAX_VALUE.

6 голосов
/ 23 августа 2010

Похоже, что ваш вопрос имеет совершенно другое содержание, чем заголовок.

Вы уже ответили на вопрос в заголовке (Integer.MAX_VALUE возвращается). И нет: нет никакого способа узнать «истинный» размер с помощью обычных API-интерфейсов, безопасных для итерации по сбору и подсчету (конечно, используя long).

Если вы хотите сохранить Set из int значений и знаете, что диапазон значений и может стать очень большим, тогда BitSet может быть лучшей реализацией:

import java.util.*;
import java.lang.management.*;

public final class IntegersInBitSetMemoryConsumption {
  private final static int MILLION = 1000 * 1000;

  public static void main(String... args) {
    BitSet set = new BitSet(Integer.MAX_VALUE);

    for (int i = 1;; ++i) {
      if ((i % MILLION) == 0) {
        int milsOfEntries = (i / MILLION);
        long mbytes = ManagementFactory.getMemoryMXBean().
            getHeapMemoryUsage().getUsed() / MILLION;
        double ratio = mbytes / milsOfEntries;
        System.out.println(milsOfEntries + " mil, " + mbytes + " MiB used, "
            + " ratio of bytes per entry: " + ratio);
      }

      set.set(i);
    }
  }
}

Это создаст структуру данных постоянного размера, которая может хранить все значения внутри диапазона без изменения размера и занимать относительно небольшой объем памяти (1 бит на возможное значение плюс некоторые издержки).

Однако этот метод имеет два недостатка:

  • он не поддерживает отрицательные int значения
  • не предоставляет Set API

И то, и другое можно легко обойти, написав оболочку, которая использует два объекта BitSet (возможно, лениво распределенных) для хранения положительного и отрицательного диапазона значений соответственно и реализует методы адаптера для интерфейса Set.

3 голосов
/ 23 августа 2010

Из исходного кода:

 /**
 * Returns the number of elements in this collection.  If this collection
 * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
 * <tt>Integer.MAX_VALUE</tt>.
 * 
 * @return the number of elements in this collection
 */
int size();
0 голосов
/ 23 августа 2010

Общий ответ для любой реальной архитектуры процессора в том, что вы просто не можете. Причина проста: не может быть больше выделенных объектов (размером не менее 1 слова), чем адресуемой памяти.

Конечно, учитывая виртуальную природу JVM, есть сценарий, где это может произойти. int всегда будет иметь 32-битную подпись, и вы можете внедрить и запустить JVM на 64-битной машине, где можно адресовать более 2 ГБ памяти.

В этом случае документация говорит нам, что Integer.MAX_INT будет возвращено ... И это большая проблема, потому что любой цикл, в котором для остановки используется целочисленная переменная, полагающаяся на i < col.size(), будет работать вечно (хотя я думаю, что все, что повторяется 2**31-1 раз, заняло бы достаточно много времени, чтобы вы все равно захотели убить процесс).

...