Java for Beginner
673 subscribers
541 photos
155 videos
12 files
827 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Утечка памяти в Java

Утечка памяти — это состояние в приложении, когда объекты, которые больше не используются, продолжают оставаться в памяти, не подлежащие сборке мусора. В отличие от традиционных языков программирования, таких как C/C++, Java имеет встроенный сборщик мусора (Garbage Collector), который автоматически освобождает память, занятую ненужными объектами. Однако это не гарантирует, что утечки памяти полностью исключены.

В Java утечки памяти случаются, когда объекты, которые больше не нужны программе, имеют сильные ссылки и не могут быть удалены сборщиком мусора. Это приводит к постепенному увеличению использования памяти и, как следствие, к ухудшению производительности или даже к OutOfMemoryError. В этой статье мы рассмотрим, что представляет собой утечка памяти в Java, каковы её причины, как её обнаружить и предотвратить.

1. Что такое утечка памяти в Java?

Утечка памяти в Java происходит, когда приложение продолжает хранить ссылки на ненужные объекты. Эти объекты становятся неподходящими для сборки мусора, и их память не освобождается, несмотря на отсутствие практического использования. Такая ситуация может возникать по нескольким причинам: неправильное использование коллекций, постоянные ссылки на анонимные или вложенные классы, ошибки при обработке потоков и многое другое.


Пример классической утечки памяти:
import java.util.ArrayList;
import java.util.List;

public class MemoryLeakExample {
private static List<Object> list = new ArrayList<>();

public static void main(String[] args) {
// Создаем утечку памяти, добавляя объекты в список, но никогда их не удаляя
while (true) {
list.add(new Object());
}
}
}
В этом примере список list продолжает расти, так как новые объекты постоянно добавляются, но никогда не удаляются. В итоге программа столкнется с OutOfMemoryError.


2. Виды утечек памяти в Java

Утечки памяти в Java можно классифицировать на несколько основных типов:

2.1 Постепенная утечка (Heap Leaks)

Этот тип утечки возникает, когда объекты продолжают оставаться в памяти, даже если они больше не используются. Например, большие коллекции, которые постепенно увеличиваются и никогда не очищаются.
public class GradualMemoryLeak {
private static List<String> cache = new ArrayList<>();

public void addData() {
for (int i = 0; i < 1000; i++) {
cache.add("Data-" + i);
}
}
}
Если метод addData вызывается многократно без очистки списка cache, то список будет постоянно увеличиваться, вызывая утечку памяти.


2.2 Утечка в статических полях (Static Field Leaks)

Статические поля живут на протяжении всего времени работы приложения и, если на объект ссылается статическое поле, объект также не подлежит сборке мусора.
public class StaticFieldMemoryLeak {
private static List<Object> staticList = new ArrayList<>();

public void addObject(Object obj) {
staticList.add(obj);
}
}
Здесь статическое поле staticList сохраняет все добавленные объекты, и они никогда не удаляются, что приводит к утечке памяти.


2.3 Утечка в слушателях (Listener Leaks)

Этот тип утечки происходит, когда слушатели (listeners) или наблюдатели (observers) не удаляются после завершения работы. Например, при использовании событийных моделей или слушателей интерфейсов (GUI-компоненты, потоки).
public class EventListenerMemoryLeak {
public static void main(String[] args) {
JFrame frame = new JFrame("Example");
JButton button = new JButton("Click Me");
button.addActionListener(e -> System.out.println("Button clicked!"));

frame.add(button);
frame.setSize(200, 200);
frame.setVisible(true);
// Закрываем окно, но слушатель остается в памяти
frame.dispose();
}
}
В этом примере, если не удалить слушатели ActionListener перед dispose(), объект button и все связанные объекты будут продолжать удерживаться в памяти.


#Java #Training #Medium #Memory_Leak
2.4 Утечка в потоках (Thread Leaks)

Когда потоки продолжают выполняться или ожидают события, они могут удерживать ссылки на объекты. Например, использование ThreadLocal или неправильное завершение потоков.

public class ThreadMemoryLeak {
public void createThread() {
Thread thread = new Thread(() -> {
try {
Thread.sleep(10000); // Ожидание в течение длительного времени
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
}
}
Если потоки создаются многократно и не завершаются корректно, то их память будет постепенно заполняться.


3. Методы обнаружения утечек памяти

Для диагностики и предотвращения утечек памяти в Java можно использовать несколько методов:


Профайлеры: Инструменты, такие как VisualVM, Eclipse MAT, и JProfiler, позволяют отслеживать использование памяти и находить объекты, которые занимают много места.
Анализ дампа памяти (Heap Dump): Дамп памяти представляет собой снимок текущего состояния кучи JVM. Его можно проанализировать с помощью jmap или инструментов, таких как Eclipse MAT.
Инструменты для мониторинга: Использование jconsole, jvisualvm или других инструментов для мониторинга позволяет наблюдать за временем жизни объектов и отслеживать изменения.


Пример использования jmap для создания дампа памяти:
jmap -dump:format=b,file=heap_dump.hprof <PID>


4. Способы предотвращения утечек памяти

Чтобы предотвратить утечки памяти в Java, рекомендуется следовать следующим принципам:


Удаление ненужных ссылок:
Освобождайте ссылки на объекты, как только они больше не нужны.

Избегайте использования статических полей для временных объектов:

Статические поля сохраняют объекты на протяжении всего времени работы приложения.

Удаление слушателей:
Удаляйте слушатели (listeners) и наблюдатели (observers), когда они больше не нужны.

Использование WeakReference и SoftReference:
Для объектов, которые можно удалять при недостатке памяти, используйте слабые и мягкие ссылки.

Проверка потоков:

Убедитесь, что потоки завершаются корректно и не продолжают работать, удерживая ненужные ссылки.

Применение try-with-resources:
Использование try-with-resources для закрытия ресурсов, таких как потоки и соединения, предотвращает утечки ресурсов.

Пример корректного использования try-with-resources:
import java.io.*;

public class TryWithResourcesExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
В этом примере ресурс автоматически закрывается, даже если возникает исключение.


#Java #Training #Medium #Memory_Leak
Недавнее обсуждение многопоточности в нашем чате, подтолкнуло меня восполнить недостатки знаний в этой области Java. 🧐

Дополняю темы про распространенные ошибки многопоточности.

Распространенные ошибки многопоточности

Race Condition (Состояние гонки)

Race Condition — это ситуация, когда поведение программы зависит от порядка или своевременности выполнения потоков. Оно возникает, когда несколько потоков одновременно обращаются к одному и тому же ресурсу (например, переменной или объекту), и хотя бы один из них изменяет его.

Пример Race Condition:
public class Counter {
private int count = 0;

public void increment() {
count++; // Неатомарная операция: чтение, увеличение, запись
}

public int getCount() {
return count;
}
}

public class RaceConditionExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();

Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});

Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println("Final count: " + counter.getCount()); // Ожидаем 2000, но результат может быть меньше
}
}


Почему Race Condition?

count++ не является атомарной операцией.

Она состоит из трех шагов:
Чтение текущего значения count.
Увеличение значения.
Запись нового значения обратно в переменную.


Если оба потока прочитают одно и то же значение до записи, итоговое значение будет некорректным.

Deadlock (Взаимная блокировка)

Deadlock — ситуация, при которой два или более потоков блокируют друг друга, ожидая освобождения ресурсов.

Пример Deadlock:
public class DeadlockExample {

private final Object lock1 = new Object();
private final Object lock2 = new Object();

public void method1() {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock1...");
try { Thread.sleep(50); } catch (InterruptedException e) {}

synchronized (lock2) {
System.out.println("Thread 1: Acquired lock2.");
}
}
}

public void method2() {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock2...");
try { Thread.sleep(50); } catch (InterruptedException e) {}

synchronized (lock1) {
System.out.println("Thread 2: Acquired lock1.");
}
}
}

public static void main(String[] args) {
DeadlockExample demo = new DeadlockExample();

Thread t1 = new Thread(demo::method1);
Thread t2 = new Thread(demo::method2);

t1.start();
t2.start();
}
}


Что происходит:
Поток 1 захватывает lock1 и ждет lock2.
Поток 2 захватывает lock2 и ждет lock1.
Оба потока застревают, ожидая освобождения ресурсов друг от друга.


#Java #Training #Multithreading #Medium #Race_Condition #Livelock #Starvation #Thread_Interference #Memory_Consistency_Errors #Multithreading_errors
Livelock (Живая блокировка)

Livelock похож на Deadlock, но здесь потоки не блокируются, а продолжают изменять свое состояние в попытке избежать конфликта, не продвигаясь дальше.

Пример Livelock:

public class LivelockExample {

static class Worker {
private boolean active = true;

public synchronized void work(Worker other) {
while (active) {
System.out.println(Thread.currentThread().getName() + " is working...");
try { Thread.sleep(50); } catch (InterruptedException e) {}

if (other.isActive()) {
System.out.println(Thread.currentThread().getName() + " is waiting...");
continue;
}
break;
}
}

public synchronized void setActive(boolean active) {
this.active = active;
}

public synchronized boolean isActive() {
return active;
}
}

public static void main(String[] args) {
Worker w1 = new Worker();
Worker w2 = new Worker();

Thread t1 = new Thread(() -> w1.work(w2), "Worker 1");
Thread t2 = new Thread(() -> w2.work(w1), "Worker 2");

t1.start();
t2.start();
}
}


Что происходит:
Оба потока продолжают "уступать" друг другу, не завершив работу.

Starvation (Голодание)

Starvation — ситуация, когда поток постоянно лишается доступа к ресурсу из-за того, что другие потоки с более высоким приоритетом занимают его.

Причина: Использование приоритетов потоков, где высокоприоритетные потоки блокируют низкоприоритетные.

Thread Interference (Конфликт потоков)

Это ситуация, похожая на Race Condition, когда несколько потоков одновременно читают и изменяют общие данные, приводя к непредсказуемым результатам.

Пример:
Потоки выполняют операции на одной переменной без синхронизации, что приводит к некорректным итоговым значениям.

Memory Consistency Errors (Ошибки согласованности памяти)

Эти ошибки возникают, когда один поток изменяет данные, но другие потоки видят устаревшее состояние этих данных.

Причина: Использование кэша процессора.
Переменная, измененная в одном потоке, может не быть видимой для других потоков.

Решение:
Использование volatile для обеспечения видимости изменений.
private static volatile boolean running = true;

public void run() {
while (running) {
// Выполняем задачу
}
}


Как предотвратить ошибки многопоточности

Использование синхронизации:
synchronized блоки или методы.
ReentrantLock для более гибкого управления блокировкой.


Использование атомарных типов:
Классы из пакета java.util.concurrent.atomic:
AtomicInteger, AtomicBoolean, AtomicReference.


Использование высокоуровневых утилит:
ExecutorService для управления потоками.
CountDownLatch, Semaphore, CyclicBarrier.


Избегание Deadlock:
Всегда захватывать блокировки в одном и том же порядке.
Использование таймаутов при ожидании захвата.


Проверка и отладка:
Инструменты отладки (например, jstack, VisualVM) для анализа состояния потоков.
Логирование текущих блокировок и их владельцев.


#Java #Training #Multithreading #Medium #Race_Condition #Livelock #Starvation #Thread_Interference #Memory_Consistency_Errors #Multithreading_errors
Please open Telegram to view this post
VIEW IN TELEGRAM
С 31.05 по 06.06
Предыдущий пост(с 24.05 по 30.05)
Следующая неделя

Воскресный мотивационный пост:
«Ты не программируешь — ты притворяешься, что учишься»

Выбранная голосованием тема:
Архитектурный шаблон MVC в Java Spring: теория, правила, ошибки

Запись встреч:
Многопоточка во всей красе. Часть 1.

Обучающие статьи:
Типы changesets и стратегии развертывания в Liquibase
Откаты (Rollback) и теги в Liquibase
Интеграция Liquibase с другими инструментами

Глубокое изучение типа данных boolean в Java
Ссылочные типы данных в Java

Пост под которым нет поздравлений:
Сегодня каналу исполнился год! 🥳

Авторская статья (которая кому-то не понравилась):

Пагинация, которую начинаешь ненавидеть 😵

Полезные статьи и видео:
Создаём HTTP-сервер на Java NIO
Большой гайд. Пишем микросервисы на Java и Spring Boot, заворачиваем в Docker, запускаем на EKS, мониторим на Grafana

ИСТОРИЯ НЕЙРОСЕТЕЙ - ОТ ПЕРЦЕПТРОНА ДО CHATGPT

Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование

#memory
Please open Telegram to view this post
VIEW IN TELEGRAM