Відповіді:
Mindprod вказує, що відповісти на це не просто:
JVM може зберігати дані будь-яким способом, який йому подобається внутрішньо, великим або маленьким ендіаном, з будь-якою кількістю прокладки або накладних витрат, хоча примітиви повинні поводитись так, ніби вони мали офіційні розміри.
Наприклад, JVM або власний компілятор може вирішити зберігатиboolean[]
64-бітні шматки на зразокBitSet
. Це не потрібно вам говорити, доки програма дає ті самі відповіді.
- Він може виділити на стеку деякі тимчасові об'єкти.
- Це може оптимізувати деякі змінні чи виклики методів, які повністю існують, замінюючи їх константами.
- Це можуть бути версійні методи або цикли, тобто компілювати дві версії методу, кожна оптимізована під певну ситуацію, а потім вирішити наперед, яку з них викликати.
Тоді, звичайно, апаратне забезпечення та ОС мають багатошарові кеші, на чіпі-кеші, кешах SRAM, кешах DRAM, звичайному робочому наборі оперативної пам’яті та зберіганні резервних копій на диску. Ваші дані можуть дублюватися на кожному рівні кешу. Вся ця складність означає, що ви можете дуже грубо передбачити споживання оперативної пам’яті.
Ви можете використовувати Instrumentation.getObjectSize()
для оцінки оцінки обсягу споживаних об'єктів.
Для візуалізації фактичного макета об’єкта, сліду та посилань можна скористатися інструментом JOL (Java Object Layout) .
У сучасному 64-бітному JDK об’єкт має 12-байтовий заголовок, доповнений кратним 8 байт, тому мінімальний розмір об'єкта становить 16 байт. Для 32-розрядних JVM-файлів накладні витрати складають 8 байтів, додані до кратного 4 байти. (З відповіді Дмитра Spikhalskiy в , відповідь Jayen в і JavaWorld .)
Зазвичай посилання - це 4 байти на 32-бітових платформах або на 64-бітових платформах до -Xmx32G
; і 8 байт вище 32Gb ( -Xmx32G
). (Див. Посилання стислих об'єктів .)
Як результат, 64-бітовому JVM зазвичай потрібно 30-50% більше місця для купи. ( Чи слід використовувати 32- або 64-розрядний JVM?, 2012, JDK 1.7)
Упаковані упаковки мають накладні витрати порівняно з примітивними типами (від JavaWorld ):
Integer
: 16-байтовий результат трохи гірший, ніж я очікував, тому щоint
значення може вміститися у всього 4 зайвих байта. ВикористанняInteger
коштує мені 300-відсоткову накладну пам'ять порівняно з тим, коли я можу зберігати значення як примітивний тип
Long
: 16 байт: Очевидно, що фактичний розмір об'єкта в купі підлягає вирівнюванню пам'яті на низькому рівні, виконаному певною реалізацією JVM для певного типу процесора. Схоже, щоLong
це 8 байт накладних витрат об'єкта, плюс 8 байт більше фактичного довгого значення. Навпаки,Integer
мав невикористаний 4-байтний отвір, швидше за все, тому що JVM I використовує сили вирівнювання об'єктів на 8-байтовій межі слова.
Інші контейнери теж дорогі:
Багатовимірні масиви : це ще один сюрприз.
Розробники зазвичай використовують такі конструкції, якint[dim1][dim2]
чисельні чи наукові обчислення.У
int[dim1][dim2]
екземплярі масиву кожен вкладенийint[dim2]
масив єObject
власним правом. Кожен додає назви звичайного 16-байтового масиву. Коли мені не потрібен трикутний або нерівний масив, що представляє собою чисті накладні витрати. Вплив зростає, коли розміри масиву сильно відрізняються.Наприклад,
int[128][2]
екземпляр займає 3600 байт. Порівняно з 1040 байтами, які використовуєint[256]
екземпляр (який має однакову ємність), 3600 байт представляють 246 відсотків накладних витрат. В крайньому випадкуbyte[256][1]
, коефіцієнт накладних витрат становить майже 19! Порівняйте це із ситуацією C / C ++, у якій той самий синтаксис не додає накладних даних.
String
:String
зростання пам’яті a відслідковує зростання внутрішнього масиву символів. ОднакString
клас додає ще 24 байти накладних витрат.Якщо немає порожнього
String
розміру 10 символів або менше, додана накладна вартість відносно корисного корисного навантаження (2 байти за кожну таблицю плюс 4 байти в довжину) становить від 100 до 400 відсотків.
Розглянемо цей приклад об’єкта :
class X { // 8 bytes for reference to the class definition
int a; // 4 bytes
byte b; // 1 byte
Integer c = new Integer(); // 4 bytes for a reference
}
Наївна сума підказує, що екземпляр X
використовує 17 байт. Однак, завдяки вирівнюванню (також його називають padding), JVM виділяє пам'ять у кратних 8 байт, тому замість 17 байт вона виділить 24 байти.
Це залежить від архітектури / jdk. Для сучасної архітектури JDK та 64 біт об’єкт має 12-байтовий заголовок та підкладку на 8 байт - тому мінімальний розмір об'єкта становить 16 байт. Ви можете використовувати інструмент під назвою " Object Layout" для визначення розміру та отримання детальної інформації про макет об'єкта та внутрішню структуру будь-якого об'єкта або здогадуватися про цю інформацію за посиланням класу. Приклад виводу для Integer для мого оточення:
Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
java.lang.Integer object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int Integer.value N/A
Instance size: 16 bytes (estimated, the sample instance is not available)
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Так, для Integer розмір екземпляра становить 16 байт, тому що 4-байтний інт ущільнюється на місці відразу після заголовка та перед межею прокладки.
Зразок коду:
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.util.VMSupport;
public static void main(String[] args) {
System.out.println(VMSupport.vmDetails());
System.out.println(ClassLayout.parseClass(Integer.class).toPrintable());
}
Якщо ви використовуєте maven, щоб отримати JOL:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.3.2</version>
</dependency>
Кожен об’єкт має певні накладні витрати для відповідної інформації монітора та типу, а також самих полів. Крім того, поля можуть бути розкладені досить багато, однак JVM вважає за потрібне (я вважаю) - але, як показано в іншій відповіді , принаймні деякі JVM будуть спакуватися досить щільно. Розглянемо такий клас:
public class SingleByte
{
private byte b;
}
проти
public class OneHundredBytes
{
private byte b00, b01, ..., b99;
}
У 32-розрядному JVM я очікував, що 100 екземплярів SingleByte
займуть 1200 байт (8 байтів накладних витрат + 4 байти для поля за рахунок заміщення / вирівнювання). Я б очікував, що один екземпляр OneHundredBytes
займе 108 байт - накладні, а потім 100 байт, запаковані. Він, безумовно, може змінюватися залежно від JVM - одна реалізація може вирішити не упаковувати поля OneHundredBytes
, приводячи до цього, займаючи 408 байт (= 8 байт накладних витрат + 4 * 100 вирівняних / прокладених байтів). На 64-бітному JVM накладні витрати можуть бути і більшими (не впевнені).
EDIT: Дивіться коментар нижче; мабуть, HotSpot прокладає до 8-ти байтних меж замість 32, тому кожен екземпляр SingleByte
займає 16 байт.
Так чи інакше, "єдиний великий об'єкт" буде принаймні настільки ж ефективним, як і кілька малих об'єктів - для простих випадків, як це.
Загальну використану / вільну пам'ять програми можна отримати в програмі через
java.lang.Runtime.getRuntime();
Виконання має кілька методів, що стосуються пам'яті. Наступний приклад кодування демонструє його використання.
package test;
import java.util.ArrayList;
import java.util.List;
public class PerformanceTest {
private static final long MEGABYTE = 1024L * 1024L;
public static long bytesToMegabytes(long bytes) {
return bytes / MEGABYTE;
}
public static void main(String[] args) {
// I assume you will know how to create a object Person yourself...
List < Person > list = new ArrayList < Person > ();
for (int i = 0; i <= 100000; i++) {
list.add(new Person("Jim", "Knopf"));
}
// Get the Java runtime
Runtime runtime = Runtime.getRuntime();
// Run the garbage collector
runtime.gc();
// Calculate the used memory
long memory = runtime.totalMemory() - runtime.freeMemory();
System.out.println("Used memory is bytes: " + memory);
System.out.println("Used memory is megabytes: " + bytesToMegabytes(memory));
}
}
Здається, що кожен об'єкт має накладні витрати в 16 байт на 32-бітних системах (і 24-байтових на 64-бітних системах).
http://algs4.cs.princeton.edu/14analysis/ є хорошим джерелом інформації. Одним із прикладів багатьох хороших є наступний.
http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf також дуже інформативний, наприклад:
Чи простір пам'яті, який споживає один об'єкт зі 100 атрибутами, такий самий, як у 100 об'єктів, з одним атрибутом кожного
Немає.
Скільки пам’яті виділено для об’єкта?
Скільки додаткового простору використовується при додаванні атрибута?
Питання буде дуже широким.
Це залежить від змінної класу або ви можете викликати як використання пам'яті в Java.
Він також має деякі додаткові вимоги до пам'яті для заголовків та посилань.
Пам'ять купи, що використовується об’єктом Java, включає
пам'ять для примітивних полів відповідно до їх розміру (див. нижче Розміри примітивних типів);
пам'ять для еталонних полів (по 4 байти кожне);
заголовок об'єкта, що складається з декількох байтів інформації про "ведення господарства";
Об'єкти в Java також вимагають деякої "домашньої" інформації, наприклад запису об'єкта класу, ідентифікаторів та статусних прапорів, наприклад, доступність об'єкта в даний момент, синхронізація заблокована тощо.
Розмір заголовка об’єкта Java змінюється на 32 та 64 бітових jvm.
Хоча це основні споживачі пам’яті, jvm також вимагає додаткових полів, як-от для вирівнювання коду тощо
Розміри примітивних типів
булева & байт - 1
char & short - 2
int & float - 4
довгий і подвійний - 8
Я отримав дуже хороші результати від підходу java.lang.instrument.Instrumentation, згаданого в іншій відповіді. Про добрі приклади його використання див. Запис, лічильник пам'яті приладів з бюлетеня JavaSpecialists та бібліотеку java.sizeOf у SourceForge.
Якщо це комусь корисно, ви можете завантажити з мого веб-сайту невеликий Java-агент для запиту на використання пам'яті об'єкта . Це дозволить вам також запитувати "глибоке" використання пам'яті.
(String, Integer)
використовує кеш Guava, на елемент. Дякую!
Правила щодо споживання пам’яті залежать від реалізації JVM та архітектури процесора (32 біт проти 64 біта, наприклад).
Щоб отримати детальні правила для СВ СВ, перегляньте мій старий блог
З повагою, Маркусе