Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Размер ссылки в Java
Размер ссылки на объект в Java зависит от архитектуры JVM и может быть 4 или 8 байт:
На 32-битной JVM
- 4 байта (32 бита) для обычных ссылок
На 64-битной JVM
- 8 байт (64 бита) для обычных ссылок без оптимизаций
- 4 байта (32 бита) при включении
CompressedOops(сжатые ссылки)
CompressedOops — Compressed Ordinary Object Pointers
CompressedOops включена по умолчанию на 64-битной JVM когда heap < 32GB. Это экономит память и улучшает кэш-производительность.
public class ObjectSizeExample {
public static void main(String[] args) {
// Получить размер объекта
Unsafe unsafe = getUnsafe();
// Простой объект
SimpleObject obj = new SimpleObject();
long size = unsafe.sizeOf(obj);
System.out.println("Размер SimpleObject: " + size + " bytes");
// На 64-bit JVM с CompressedOops:
// Object header: 12 bytes (mark + class reference сжатая)
// - mark word: 8 bytes
// - class reference: 4 bytes (сжато)
// Alignment padding: 4 bytes
// Total: 16 bytes
// На 64-bit JVM без CompressedOops:
// Object header: 16 bytes
// - mark word: 8 bytes
// - class reference: 8 bytes (полная)
// Total: 16 bytes (уже выровнено)
// С полем int:
IntFieldObject intObj = new IntFieldObject();
// Object header: 12 bytes
// int field: 4 bytes
// Padding: 0 bytes (итого 16, выровнено)
// Total: 16 bytes
// Массив ссылок
Object[] refArray = new Object[100];
// Array header: 16 bytes
// References: 100 * 4 bytes = 400 bytes (с CompressedOops)
// Total: 416 bytes
}
static class SimpleObject {
// Нет полей
}
static class IntFieldObject {
int value;
}
// Получить Unsafe для расчёта размеров
public static Unsafe getUnsafe() {
try {
java.lang.reflect.Field f =
Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
Анатомия объекта в памяти
64-bit JVM с CompressedOops:
┌─────────────────────────────────┐
│ mark word (8 bytes) │ Содержит: hashcode, lock, GC bits
├─────────────────────────────────┤
│ class reference (4 bytes) │ ← Сжатая ссылка на класс
├─────────────────────────────────┤
│ field 1: int (4 bytes) │
├─────────────────────────────────┤
│ field 2: Object (4 bytes) │ ← Сжатая ссылка
├─────────────────────────────────┤
│ alignment padding (as needed) │
└─────────────────────────────────┘
Минимальный размер объекта: 16 bytes (выравнено на 8)
Практический пример: Размер коллекций
public class CollectionMemoryFootprint {
public static void main(String[] args) {
// ArrayList<String> с 100 элементами
// =====================================
// ArrayList object:
// - Object header: 12 bytes
// - elementData reference: 4 bytes
// - size (int): 4 bytes
// - padding: 0 bytes
// Total: 16 bytes + 8 bytes (alignment) = 24 bytes
// elementData array (Object[]):
// - Array header: 16 bytes
// - References: 100 * 4 bytes (CompressedOops) = 400 bytes
// Total: 416 bytes
// Итого для ArrayList: 24 + 416 = 440 bytes
// + память самих String объектов
List<String> list = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
list.add("String" + i);
}
// Приблизительная память: 440 + (100 * ~50 bytes per String)
// ≈ 5440 bytes
// HashMap<String, Integer> с 100 элементами
// ============================================
// HashMap object: ≈ 48 bytes
// Node[] table: 1024 * 4 = 4096 bytes (при resize)
// 100 Node objects: 100 * 32 = 3200 bytes
// (Node: 8 hash + 4 key ref + 4 value ref + 4 next ref + 12 header)
// Total: ≈ 7344 bytes
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < 100; i++) {
map.put("key" + i, i);
}
}
}
Как измерить реальный размер объекта
import sun.misc.Unsafe;
public class ObjectSizeCalculator {
private static final Unsafe UNSAFE = getUnsafe();
public static long sizeOf(Object obj) {
return UNSAFE.sizeOf(obj);
}
public static void main(String[] args) {
User user = new User(1L, "John", 30);
System.out.println("Object: " + user);
System.out.println("Size: " + sizeOf(user) + " bytes");
// Выход на 64-bit JVM:
// Object header: 12 bytes
// long id: 8 bytes
// String name reference: 4 bytes
// int age: 4 bytes
// padding: 0 bytes
// Total: 28 bytes (выравнится до 32)
// Плюс размер самого String объекта (если считать рекурсивно)
}
public static class User {
private long id;
private String name;
private int age;
public User(long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
private static Unsafe getUnsafe() {
try {
java.lang.reflect.Field f =
Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
JVM флаги для управления ссылками
# Проверить текущие настройки:
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompressedOopsMode \
-version
# Включить CompressedOops явно:
java -XX:+UseCompressedOops MyApp
# Отключить CompressedOops:
java -XX:-UseCompressedOops MyApp
# Максимальный heap с CompressedOops:
java -XX:+UseCompressedOops -Xmx32G MyApp # Оптимально
java -XX:+UseCompressedOops -Xmx34G MyApp # Может отключить CompressedOops
# Объём памяти для одной ссылки:
java -XX:+PrintCompressedOopsMode -XX:+LogCompilation MyApp 2>&1 | grep oops
Практические последствия
public class MemoryOptimizationPractices {
// 1. Минимизируй ссылки на объекты
// ❌ ПЛОХО: много ссылок
class BadDesign {
User owner;
User creator;
User modifier;
List<User> viewers;
Map<String, User> permissions;
}
// ✅ ХОРОШО: используй ID вместо полных объектов где возможно
class GoodDesign {
long ownerId; // 8 bytes вместо reference
long creatorId; // 8 bytes
long modifierId; // 8 bytes
List<Long> viewerIds; // ссылки на Long вместо User
}
// 2. Используй примитивные типы где возможно
// ❌ ПЛОХО
class BadDates {
LocalDateTime created; // Object reference (4 bytes)
LocalDateTime modified; // Object reference (4 bytes)
}
// ✅ ХОРОШО
class GoodDates {
long createdTime; // 8 bytes (примитив)
long modifiedTime; // 8 bytes (примитив)
}
// 3. Переиспользуй объекты вместо создания новых
// ❌ ПЛОХО: создание новых StringBuilder в цикле
public String processBadly(List<String> items) {
String result = "";
for (String item : items) {
result += item; // Новый String и StringBuilder каждый раз
}
return result;
}
// ✅ ХОРОШО: переиспользование StringBuilder
public String processWell(List<String> items) {
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item);
}
return sb.toString();
}
}
Итоговые цифры
| Параметр | 32-bit JVM | 64-bit JVM (без CompressedOops) | 64-bit JVM (с CompressedOops) |
|---|---|---|---|
| Object reference | 4 bytes | 8 bytes | 4 bytes |
| Object header | 8 bytes | 16 bytes | 12 bytes |
| Минимальный объект | 16 bytes | 16 bytes | 16 bytes |
| Массив из 100 ссылок | 416 bytes | 816 bytes | 416 bytes |
На практике: Если ты используешь 64-bit JVM (а это почти всегда), то ссылка занимает 4 байта благодаря CompressedOops, что автоматически включено при heap < 32GB.