Яке споживання пам'яті об’єкта на Java?


216

Чи простір пам'яті, який споживає один об'єкт зі 100 атрибутами, такий самий, як у 100 об'єктів, з одним атрибутом кожного

Скільки пам’яті виділено для об’єкта?
Скільки додаткового простору використовується при додаванні атрибута?

Відповіді:


180

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 байти.


int [128] [6]: 128 масивів з 6 ints - 768 int усього, 3072 байти даних + 2064 байт Об'єкт накладних = 5166 байт загалом. int [256]: 256 int в цілому - тому непорівнянні. int [768]: 3072 байти даних + 16 байдів накладних витрат - приблизно 3/5 місця простору 2D масиву - не зовсім 246% накладних витрат!
JeeBee

Ах, оригінальна стаття, що використовується int [128] [2] not int [128] [6] - цікаво, як це змінилося. Також показує, що крайні приклади можуть розповісти іншу історію.
JeeBee

2
Накладні витрати - 16 байт у 64-бітових JVM.
Тім Купер

3
@AlexWien: Деякі схеми збору сміття можуть накладати мінімальний розмір об'єкта, який є окремим від прокладки. Під час збору сміття, коли об’єкт буде скопійовано зі старого місця розташування в нове, старе місце розташування може більше не потребуватиме збереження даних про об'єкт, але йому потрібно буде посилатись на нове місцеположення; може також знадобитися збереження посилання на старе місце розташування об'єкта, де було виявлено перше посилання та зміщення цього посилання в межах старого об'єкта [оскільки старий об'єкт все ще може містити посилання, які ще не оброблені].
supercat

2
@AlexWien: Використання пам’яті на старому місці об’єкта для зберігання інформації про зберігання смітників дозволяє уникнути необхідності виділення іншої пам’яті для цієї мети, але може накласти мінімальний розмір об’єкта, який більший, ніж потрібно в іншому випадку. Я думаю, що принаймні одна версія .NET сміттєзбірника використовує такий підхід; це, безумовно, могло б зробити і деякі збирачі сміття Java.
supercat

34

Це залежить від архітектури / 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>

28

Кожен об’єкт має певні накладні витрати для відповідної інформації монітора та типу, а також самих полів. Крім того, поля можуть бути розкладені досить багато, однак 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 байт.

Так чи інакше, "єдиний великий об'єкт" буде принаймні настільки ж ефективним, як і кілька малих об'єктів - для простих випадків, як це.


9
Насправді один екземпляр SingleByte зайняв би 16 байт на JVM Sun, тобто 8 байт накладних витрат, 4 байти для поля, а потім 4 байти для
заміщення

6

Загальну використану / вільну пам'ять програми можна отримати в програмі через

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));
     }
 }

6

Здається, що кожен об'єкт має накладні витрати в 16 байт на 32-бітних системах (і 24-байтових на 64-бітних системах).

http://algs4.cs.princeton.edu/14analysis/ є хорошим джерелом інформації. Одним із прикладів багатьох хороших є наступний.

введіть тут опис зображення

http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-java-tutorial.pdf також дуже інформативний, наприклад:

введіть тут опис зображення


"Здається, що кожен об'єкт має накладні витрати на 16 байт у 32-бітних системах (і 24-байтових на 64-бітних системах)." Це не правильно, принаймні для поточних JDK. Подивіться мою відповідь на прикладі Integer. Накладні витрати об'єкта - не менше 12 байт для заголовка для 64-бітної системи та сучасного JDK. Може бути більше, ніж прокладка, залежить від фактичного розташування полів в об'єкті.
Дмитро Спихальський

Другий посилання на ефективний підручник для Java, схоже, мертвий - я отримую "Заборонено".
цлейсон

6

Чи простір пам'яті, який споживає один об'єкт зі 100 атрибутами, такий самий, як у 100 об'єктів, з одним атрибутом кожного

Немає.

Скільки пам’яті виділено для об’єкта?

  • Накладні витрати - 8 байт на 32-бітових, 12 байт на 64-бітних; а потім округляють до кратного 4 байтів (32-бітових) або 8 байтів (64-бітових).

Скільки додаткового простору використовується при додаванні атрибута?

  • Атрибути варіюються від 1 байта (8 байт) до 8 байт (довгий / подвійний), але посилання є або 4 байтами, або 8 байтами, залежно не від того, чи це 32 біт, або 64 біт, а скоріше, чи -Xmx <32Gb або> = 32Gb: типово 64 -bit JVM мають оптимізацію під назвою "-UseCompressionOops", яка стискає посилання на 4 байти, якщо купа нижче 32Gb.

1
char - 16 біт, а не 8 біт.
comonad

право ти. хтось, здається, відредагував мою оригінальну відповідь
Jayen

5

Ні, реєстрація об'єкта також займає небагато пам'яті. 100 об'єктів з 1 атрибутом займуть більше пам’яті.


4

Питання буде дуже широким.

Це залежить від змінної класу або ви можете викликати як використання пам'яті в Java.

Він також має деякі додаткові вимоги до пам'яті для заголовків та посилань.

Пам'ять купи, що використовується об’єктом Java, включає

  • пам'ять для примітивних полів відповідно до їх розміру (див. нижче Розміри примітивних типів);

  • пам'ять для еталонних полів (по 4 байти кожне);

  • заголовок об'єкта, що складається з декількох байтів інформації про "ведення господарства";

Об'єкти в Java також вимагають деякої "домашньої" інформації, наприклад запису об'єкта класу, ідентифікаторів та статусних прапорів, наприклад, доступність об'єкта в даний момент, синхронізація заблокована тощо.

Розмір заголовка об’єкта Java змінюється на 32 та 64 бітових jvm.

Хоча це основні споживачі пам’яті, jvm також вимагає додаткових полів, як-от для вирівнювання коду тощо

Розміри примітивних типів

булева & байт - 1

char & short - 2

int & float - 4

довгий і подвійний - 8


Читачі можуть також вважати цей документ дуже яскравим: cs.virginia.edu/kim/publicity/pldi09tutorials/…
чудовий


2

Якщо це комусь корисно, ви можете завантажити з мого веб-сайту невеликий Java-агент для запиту на використання пам'яті об'єкта . Це дозволить вам також запитувати "глибоке" використання пам'яті.


Це чудово підходило для отримання приблизної оцінки обсягу пам’яті, яку (String, Integer)використовує кеш Guava, на елемент. Дякую!
Стів К

1

ні, 100 малих об'єктів потребує більше інформації (пам'яті), ніж один великий.


0

Правила щодо споживання пам’яті залежать від реалізації JVM та архітектури процесора (32 біт проти 64 біта, наприклад).

Щоб отримати детальні правила для СВ СВ, перегляньте мій старий блог

З повагою, Маркусе


Я впевнений, що Sun Java 1.6 64bit, для простого об'єкта потрібно 12 байт + 4 прокладки = 16; об'єкт + одне ціле поле = 12 + 4 = 16
AlexWien

Ви закрили свій блог?
Йохан Буле

Не дуже, Не впевнений, що SAP-блоги якось перемістилися. Більшість із них можна знайти тут kohlerm.blogspot.com
kohlerm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.