← Назад к вопросам

Сколько занимает места ссылка в Java?

1.0 Junior🔥 221 комментариев
#Основы Java

Комментарии (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 JVM64-bit JVM (без CompressedOops)64-bit JVM (с CompressedOops)
Object reference4 bytes8 bytes4 bytes
Object header8 bytes16 bytes12 bytes
Минимальный объект16 bytes16 bytes16 bytes
Массив из 100 ссылок416 bytes816 bytes416 bytes

На практике: Если ты используешь 64-bit JVM (а это почти всегда), то ссылка занимает 4 байта благодаря CompressedOops, что автоматически включено при heap < 32GB.

Сколько занимает места ссылка в Java? | PrepBro