Трудности определения размера списка Java List в памяти

Что-то странное происходит в демонстрационном коде, который я создаю, чтобы проиллюстрировать компромиссы между Java ArrayList и LinkedList. Проблема: я наблюдаю увеличивать в свободной памяти после создания ArrayList размером 100000. Что мне здесь не хватает?

Наблюдаемый результат:

Total memory: 514850816 bytes   Free memory: 511852088 bytes    Used memory: 2998728 bytes
ArrayList random access: 8 ms
ArrayList rotation: 740 ms
Total memory: 514850816 bytes   Free memory: 512526416 bytes    Used memory: 2324400 bytes
Approximate ArrayList memory usage with 100000 4-byte integers: -674328 bytes (!?!?!?)
LinkedList random access: 2345 ms
LinkedList rotation: 5 ms
Total memory: 514850816 bytes   Free memory: 507136648 bytes    Used memory: 7714168 bytes
Approximate LinkedList memory usage with 100000 4-byte integers: 5389768 bytes
100000
100000
81838
46976

Обратите внимание, что разница в свободной памяти составляет отрицательный после увеличения использования памяти с помощью ArrayList. См. Строку вывода выше с "(!?!?!?)"

Это код, который сгенерировал вывод:

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

    // An ArrayList is like a dynamically resizable array
    // - has a capacity that can be beyond size()  (trimToSize() for space efficiency)
    // - good for random access
    // - poor for insertion (must shift all elements to different indices)

    // A LinkedList is a doubly-linked list with links forward and backward between nodes
    // - poor for random access
    // - good for insertion

    System.gc(); // force system garbage collection
    long totalMemory = Runtime.getRuntime().totalMemory();
    long freeMemory = Runtime.getRuntime().freeMemory();
    long memoryBefore = totalMemory - freeMemory;
    System.out.println("Total memory: " + Runtime.getRuntime().totalMemory() + " bytes \tFree memory: " + Runtime.getRuntime().freeMemory()
            + " bytes \tUsed memory: " + memoryBefore + " bytes");

    final int SIZE = 100000;
    // ArrayList demo:
    ArrayList<Integer> aList = new ArrayList<>();
    for (int i = 0; i < SIZE; i++)
        aList.add(i);

    // Random access
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < SIZE / 2; i++) {
        int value = aList.get((int) (SIZE * Math.random()));
        value = value + 1;
    }
    long endTime = System.currentTimeMillis();
    System.out.printf("ArrayList random access: %d ms\n", endTime - startTime);

    // Right rotate half of elements, one at a time
    startTime = System.currentTimeMillis();
    for (int i = 0; i < SIZE / 2; i++) {
        int value = aList.remove(aList.size() - 1);
        aList.add(0, value);
    }
    endTime = System.currentTimeMillis();
    System.out.printf("ArrayList rotation: %d ms\n", endTime - startTime);

    aList.trimToSize(); // ArrayLists are Object[] underneath that are copied to and replaced by arrays doubled in size as needed.
      // trimToSize() trims the array to its currently needed size
    System.gc(); // force system garbage collection
    totalMemory = Runtime.getRuntime().totalMemory();
    freeMemory = Runtime.getRuntime().freeMemory();
    long memoryAfterArrayList = totalMemory - freeMemory;
    System.out.println("Total memory: " + totalMemory + " bytes \tFree memory: " + freeMemory
            + " bytes \tUsed memory: " + memoryAfterArrayList + " bytes");
    System.out.println("Approximate ArrayList memory usage with " + SIZE + " 4-byte integers: " + (memoryAfterArrayList - memoryBefore) + " bytes (!?!?!?)");

    // LinkedList demo:

    LinkedList<Integer> lList = new LinkedList<>();
    for (int i = 0; i < SIZE; i++)
        lList.add(i);

    // Random access
    startTime = System.currentTimeMillis();
    for (int i = 0; i < SIZE / 2; i++) {
        int value = lList.get((int) (SIZE * Math.random()));
        value = value + 1;
    }
    endTime = System.currentTimeMillis();
    System.out.printf("LinkedList random access: %d ms\n", endTime - startTime);
    // NOTE: This is why you should use an iterator/for-each loop
    // for a LinkedList rather than a for loop with .get(index) operations.

    // Right rotate half of elements, one at a time
    startTime = System.currentTimeMillis();
    for (int i = 0; i < SIZE / 2; i++) {
        lList.addFirst(lList.removeLast());
    }
    endTime = System.currentTimeMillis();
    System.out.printf("LinkedList rotation: %d ms\n", endTime - startTime);

    totalMemory = Runtime.getRuntime().totalMemory();
    freeMemory = Runtime.getRuntime().freeMemory();
    long memoryAfterLinkedList = totalMemory - freeMemory;
    System.out.println("Total memory: " + totalMemory + " bytes \tFree memory: " + freeMemory
            + " bytes \tUsed memory: " + memoryAfterLinkedList + " bytes");
    System.out.println("Approximate LinkedList memory usage with " + SIZE + " 4-byte integers: " + (memoryAfterLinkedList - memoryAfterArrayList) + " bytes");

    // So both ArrayList and LinkedLists are AbstractLists, but with very
    // different implementations and very different performance for different tasks.
    // Conclusion: You should know _how_ data structures are implemented (CS 216)
    // to make proper application of them.

    // To prevent any optimizing compilation that would free aList or lList before this point:
    System.out.println(aList.size());
    System.out.println(lList.size());
    System.out.println(aList.get((int) (SIZE * Math.random())));
    System.out.println(lList.get((int) (SIZE * Math.random())));
}

Кроме того, если у вас есть предложения по лучшему способу отображения использования памяти структурами данных в Java в реальном времени, я все слышу. Спасибо!

Обновление: следуя совету Йорна Ворни, я запустил это с параметром -Xlog: gc * из оболочки (а не в Eclipse, как указано выше) с Java 1.9 (Java (TM) SE Runtime Environment (сборка 9.0.1 + 11)), и получил следующий результат:

[0.019s][info][gc] Using G1
[0.088s][info][gc] GC(0) Pause Full (System.gc()) 1M->0M(8M) 6.102ms
Total memory: 8388608 bytes     Free memory: 7623384 bytes      Used memory: 765224 bytes
[0.095s][info][gc] GC(1) Pause Young (G1 Evacuation Pause) 2M->2M(8M) 1.963ms
ArrayList random access: 14 ms
[0.861s][info][gc] GC(2) Pause Young (G1 Evacuation Pause) 4M->2M(10M) 2.938ms
ArrayList rotation: 740 ms
[0.882s][info][gc] GC(3) Pause Full (System.gc()) 3M->2M(10M) 9.494ms
Total memory: 10485760 bytes    Free memory: 7602584 bytes      Used memory: 
2883176 bytes
Approximate ArrayList memory usage with 100000 4-byte integers: 2117952 bytes (!?!?!?)
[0.897s][info][gc] GC(4) Pause Young (G1 Evacuation Pause) 4M->4M(10M) 6.709ms
LinkedList random access: 2603 ms
[3.516s][info][gc] GC(5) Pause Initial Mark (G1 Evacuation Pause) 6M->6M(12M) 14.172ms
[3.516s][info][gc] GC(6) Concurrent Cycle
LinkedList rotation: 23 ms
Total memory: 12582912 bytes    Free memory: 4411232 bytes      Used memory: 
8171680 bytes
Approximate LinkedList memory usage with 100000 4-byte integers: 5288504 bytes
100000
100000
91956
67844
[3.536s][info][gc] GC(6) Pause Remark 7M->7M(12M) 2.713ms
[3.538s][info][gc] GC(6) Pause Cleanup 7M->7M(12M) 0.062ms
[3.539s][info][gc] GC(6) Concurrent Cycle 22.275ms

Предыдущий запуск был с jre1.8.0_151. Есть идеи, почему между этими версиями Java существует такая значительная разница в поведении памяти? Кроме того, Йорн Ворни, в соответствующем посте есть довольно большой список рекомендаций (спасибо!), Но есть ли какие-то конкретные рекомендации, которые вы считаете здесь наиболее полезными?

Это количество кода самый маленький, которое воспроизводит вашу проблему?

khelwood 16.04.2018 00:44

Кажется, я не могу воспроизвести это.

Jacob G. 16.04.2018 00:55

Я предлагаю использовать -Xlog:gc* (Java9 +), чтобы увидеть, что делает сборщик мусора. См. Также: stackoverflow.com/questions/504103

Jorn Vernee 16.04.2018 00:57

Обратите внимание, что System.gc() выполняет нет «принудительную сборку мусора», и в этом случае почти не используется память, это, вероятно, будет проигнорировано.

chrylis -cautiouslyoptimistic- 16.04.2018 04:59

Возможный дубликат Как лучше всего определить размер объекта в Java?

Patrick Parker 05.05.2018 22:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
5
69
0

Другие вопросы по теме