Что-то странное происходит в демонстрационном коде, который я создаю, чтобы проиллюстрировать компромиссы между 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 существует такая значительная разница в поведении памяти? Кроме того, Йорн Ворни, в соответствующем посте есть довольно большой список рекомендаций (спасибо!), Но есть ли какие-то конкретные рекомендации, которые вы считаете здесь наиболее полезными?
Кажется, я не могу воспроизвести это.
Я предлагаю использовать -Xlog:gc* (Java9 +), чтобы увидеть, что делает сборщик мусора. См. Также: stackoverflow.com/questions/504103
Обратите внимание, что System.gc() выполняет нет «принудительную сборку мусора», и в этом случае почти не используется память, это, вероятно, будет проигнорировано.
Возможный дубликат Как лучше всего определить размер объекта в Java?




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