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

Как уменьшить память на машине

1.8 Middle🔥 121 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как уменьшить потребление памяти на машине (оптимизация Java приложения)

Потребление памяти — это одна из главных причин высокой нагрузки на сервер. Вот систематический подход к диагностике и оптимизации.

1. Диагностика текущего потребления памяти

Шаг 1: Посмотрите какой процесс ест память

// На Linux
free -h                          // Общая память системы
ps aux --sort=-%mem | head -10   // Top потребителей памяти
top -b -n 1                      // Интерактивный мониторинг
df -h                            // Свободное место на диске

Шаг 2: Найдите Java процесс

jps -l                           // Список Java процессов
ps aux | grep java               // Детали Java процесса

Шаг 3: Проверьте текущие JVM параметры

jcmd <PID> VM.command_line       // Как был запущен процесс
jps -lv                          // JVM флаги для всех процессов

2. Оптимизация Heap Size (основной потребитель памяти)

Проблема: Слишком большой Heap

// Плохо - выделяет 4GB памяти
java -Xmx4G -Xms4G -jar application.jar

// Хорошо - выделяет 512MB-1GB в зависимости от нужды
java -Xmx1G -Xms512M -jar application.jar

Рекомендации по размеру:

  • Development: -Xmx512M (небольшие тесты)
  • Small app: -Xmx1G (API с несколько тысячами пользователей)
  • Medium app: -Xmx2G-4G (основная масса приложений)
  • Large app: -Xmx8G+ (сложные вычисления, большие в памяти данные)

Формула: нужно примерно 1.5-2x от пика потребления памяти в обычной работе

// Как найти нужный размер
// 1. Запустить с большим heap -Xmx8G
// 2. Мониторить использованную память
jstat -gc -h5 <PID> 1000

// 3. Посмотреть Old Generation usage
// 4. Выставить Xmx примерно в 2x от этого значения

3. Оптимизация GC (Garbage Collection)

Отслеживание GC паузы

// Включить GC логи
java -Xms512M -Xmx1G \
  -XX:+PrintGCDetails \
  -XX:+PrintGCDateStamps \
  -XX:+PrintGCTimeStamps \
  -Xloggc:gc-%t.log \
  -XX:+UseGCLogFileRotation \
  -XX:NumberOfGCLogFiles=10 \
  -XX:GCLogFileSize=100M \
  -jar application.jar

Выбор GC алгоритма

G1GC (рекомендуется для большинства):

java -Xms1G -Xmx4G \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -jar application.jar

// Преимущества:
// - Предсказуемые GC паузы
// - Хорошо масштабируется
// - Автоматически настраивается

ZGC (для низких паузы GC):

java -Xms4G -Xmx8G \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseZGC \
  -jar application.jar

// Преимущества:
// - GC паузы < 10ms даже с большим heap
// - Идеально для low-latency приложений

Shenandoah (альтернатива ZGC):

java -Xms4G -Xmx8G \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseShenandoahGC \
  -jar application.jar

4. Уменьшение размера Old Generation (OldGen)

Это место, где собирается "долгоживущий" мусор.

// Если OldGen быстро заполняется - проблема в утечке памяти
jstat -gc -h5 <PID> 1000

// Смотрим столбцы OC (Old Capacity) и OU (Old Used)
// Если OU растет со временем - утечка!

5. Анализ утечек памяти

Получить heap dump

// Способ 1: jmap
jmap -dump:live,format=b,file=heap.bin <PID>

// Способ 2: jcmd (Java 9+)
jcmd <PID> GC.heap_dump /tmp/heap.bin

// Способ 3: С флагом при запуске
java -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/tmp/heap.bin \
  -jar application.jar

Анализ heap dump

// Используйте eclipse MAT (Memory Analyzer Tool)
// Откройте heap.bin и посмотрите:
// 1. "Leak Suspects" - вероятные утечки
// 2. "Histogram" - какие объекты занимают больше памяти
// 3. "Dominator Tree" - иерархия объектов

Типичные утечки в Java коде

// Утечка 1: Static коллекции которые растут
public class Cache {
    private static Map<String, Object> cache = new HashMap<>();
    
    public static void add(String key, Object value) {
        cache.put(key, value);  // Никогда не удаляется!
    }
}

// Исправление: добавить TTL или максимальный размер
public class Cache {
    private static Map<String, CacheEntry> cache = new LinkedHashMap<String, CacheEntry>(16, 0.75f, true) {
        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            return size() > 1000;  // Максимум 1000 записей
        }
    };
}

// Утечка 2: Listeners которые не удаляются
public class EventSource {
    private List<EventListener> listeners = new ArrayList<>();
    
    public void subscribe(EventListener listener) {
        listeners.add(listener);  // Если не unsubscribe - утечка!
    }
}

// Исправление: использовать WeakReference
public class EventSource {
    private List<WeakReference<EventListener>> listeners = new ArrayList<>();
    
    public void subscribe(EventListener listener) {
        listeners.add(new WeakReference<>(listener));
    }
    
    public void fireEvent(Event event) {
        listeners.removeIf(ref -> ref.get() == null);  // Очищаем мёртвые ссылки
        listeners.forEach(ref -> ref.get().onEvent(event));
    }
}

// Утечка 3: Connection/Resource pools
public class DatabasePool {
    private Queue<Connection> pool = new LinkedList<>();
    
    public void getConnection() {
        // Если Connection никогда не возвращается - утечка!
    }
}

// Исправление: используйте try-with-resources
try (Connection conn = dataSource.getConnection()) {
    // Используем connection
}  // Автоматически закрывается

6. Оптимизация Object Allocation (объекты)

Избегайте ненужного создания объектов

// Плохо - создает новый объект каждый раз
public List<User> getUsers() {
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 1000; i++) {
        users.add(new User("User" + i, "email" + i + "@example.com"));
    }
    return users;
}

// Хорошо - переиспользуем объекты
public List<User> getUsers() {
    User user = new User();
    List<User> users = new ArrayList<>();
    for (int i = 0; i < 1000; i++) {
        user.setName("User" + i);
        user.setEmail("email" + i + "@example.com");
        users.add(new User(user));  // Клонируем если нужно
    }
    return users;
}

// Плохо - String конкатенация
String result = "";
for (int i = 0; i < 1000; i++) {
    result += "Item " + i;  // Создает новую строку каждый раз
}

// Хорошо - StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("Item ").append(i);
}
String result = sb.toString();

7. Оптимизация Collections

// Выделяйте нужный размер заранее
// Плохо - ArrayList с дефолтным размером 10, будет расти
List<Item> items = new ArrayList<>();  // capacity=10
for (int i = 0; i < 1000000; i++) {
    items.add(new Item());  // Много resize операций
}

// Хорошо - выделяем нужный размер
List<Item> items = new ArrayList<>(1000000);  // capacity=1000000

// Используйте нужные структуры
HashMap vs TreeMap vs LinkedHashMap
// HashMap - fastest но нужно больше памяти
// TreeMap - медленнее но упорядочено
// LinkedHashMap - средняя скорость, порядок вставки

// Если можно использовать массив вместо List
int[] numbers = new int[1000];  // Быстрее и меньше памяти
List<Integer> list = new ArrayList<>();  // Медленнее, больше памяти

8. Отключите ненужные библиотеки

// Посмотрите что грузится при старте
java -verbose:class -jar application.jar | head -20

// Если видите ненужные классы - исключите библиотеки
// В pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </exclusion>
    </exclusions>
</dependency>

9. Профилирование памяти

// Runtime информация
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
long maxMemory = runtime.maxMemory();

System.out.println("Used: " + usedMemory / 1024 / 1024 + "MB");
System.out.println("Max: " + maxMemory / 1024 / 1024 + "MB");
System.out.println("Free: " + runtime.freeMemory() / 1024 / 1024 + "MB");

10. Настройка системных параметров

# Уменьшить swappiness (Linux)
sudo sysctl vm.swappiness=10

# Максимум open файлов
ulimit -n 65536

# Максимум процессов
ulimit -u 65536

Чеклист оптимизации памяти

  • Определить текущее потребление: free -h, top
  • Найти Java процесс: jps -l
  • Проверить текущий heap size: jcmd <PID> VM.command_line
  • Включить GC логи и анализировать
  • Получить heap dump если подозревается утечка: jmap -dump:live,format=b
  • Проверить Old Generation на рост со временем
  • Найти static коллекции которые растут
  • Убедиться что Connections/Resources закрываются
  • Оптимизировать Object allocation
  • Выбрать нужный GC алгоритм
  • Установить подходящий Xmx (примерно 2x от пика использования)

Рекомендации по памяти в разных сценариях

Микросервис (REST API):

-Xms256M -Xmx512M -XX:+UseG1GC

Приложение с кэшем:

-Xms1G -Xmx2G -XX:+UseG1GC -XX:MaxGCPauseMillis=200

Data Processing:

-Xms4G -Xmx8G -XX:+UseG1GC -XX:MaxGCPauseMillis=500

Low Latency приложение:

-Xms8G -Xmx8G -XX:+UseZGC -XX:ZUncommitDelay=300s