{"id":20644968,"url":"https://github.com/romanow/java-memory-leaks-lecture","last_synced_at":"2026-04-07T14:02:07.683Z","repository":{"id":258225821,"uuid":"871722751","full_name":"Romanow/java-memory-leaks-lecture","owner":"Romanow","description":"Java memory leaks common cases example","archived":false,"fork":false,"pushed_at":"2025-03-17T21:32:45.000Z","size":11717,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-12T14:00:54.439Z","etag":null,"topics":["java","memory-leak"],"latest_commit_sha":null,"homepage":"https://romanow.github.io/java-memory-leaks-lecture/","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Romanow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-12T18:54:31.000Z","updated_at":"2025-03-17T21:32:50.000Z","dependencies_parsed_at":"2025-04-12T14:03:11.278Z","dependency_job_id":null,"html_url":"https://github.com/Romanow/java-memory-leaks-lecture","commit_stats":null,"previous_names":["romanow/java-memory-leaks-lecture"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Romanow/java-memory-leaks-lecture","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fjava-memory-leaks-lecture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fjava-memory-leaks-lecture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fjava-memory-leaks-lecture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fjava-memory-leaks-lecture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Romanow","download_url":"https://codeload.github.com/Romanow/java-memory-leaks-lecture/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romanow%2Fjava-memory-leaks-lecture/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31515151,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","memory-leak"],"created_at":"2024-11-16T16:18:13.781Z","updated_at":"2026-04-07T14:02:07.659Z","avatar_url":"https://github.com/Romanow.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![License: CC BY-NC-ND 4.0](https://img.shields.io/badge/License-CC%20BY--NC--ND%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-nd/4.0/)\n[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)\n\n# Делаем утечки памяти в Java быстро и без регистрации\n\n## Аннотация\n\nВсе знают, что в Java есть сборщик мусора, и она сама очищает неиспользуемую память. Но этот механизм может работать не\nвсегда идеально: можно написать код таким образом, что Java не сможет очистить память и она будет копиться пока\nприложение не упадет по OutOfMemoryError. На простых примерах рассмотрим несколько случаев, когда это может произойти. А\nв заключении поговорим как искать утечки памяти с использованием встроенных инструментов JDK.\n\n## План\n\n1. Как устроена память в Java.\n2. Как в Java осуществляется поиск неиспользуемых объектов?\n3. Рассмотрим несколько примеров, когда возникает утечка памяти:\n    * Неверная реализация `equals` и `hashCode`.\n    * Статические члены класса.\n    * Пытаемся исправить утечку памяти с помощью WeakReference.\n    * ThreadLocal переменные при использовании пула потоков.\n    * Нестатические внутренние классы.\n    * Пул строк (String interning).\n4. Как найти утечку памяти (работаем с `jmap`, `jstack` и `jhsdb`);\n5. Отладка приложения через консоль.\n6. Выводы: простые правила как не допускать утечек памяти.\n\n## Доклад\n\n### Как устроена память в Java\n\n![Java Memory](images/Java%208%20Memory.png)\n\nПамять делится на **Heap**, **Metaspace** и **Stack**.\n\n* **Heap** – основной сегмент памяти, используется для выделения памяти под объекты и JRE классы. Создание нового\n  объекта происходит в Heap, здесь работает GC.\n* **Metaspace** – хранятся метаданные о классе и статические поля: там хранятся это либо примитивы, либо ссылки на\n  объекты/массивы, которые сами по себе аллоцированы в **Heap**. **Metaspace** в Java 8 пришел на замену **PermGen**,\n  основное отличие которой — возможность динамически расширятся, ограниченная по умолчанию только размером нативной\n  памяти. Опционально можно задать размер через аргумент`-XX:MaxMetaspaceSize`. В боевых окружениях желательно всегда\n  задавать размер **Metaspace**. В случае возникновения ошибки, лечится увеличением **Metaspace**, либо добавлением\n  памяти.\n* **Stack** – стековая память в Java работает по схеме LIFO: всякий раз, когда вызывается метод, в памяти стека\n  создается новый блок, который содержит примитивы и _ссылки_ на другие объекты в методе. Каждый поток имеет свой стек,\n  примитивы и ссылки на локальные переменные хранятся в стеке. Как только метод заканчивает работу, блок также перестает\n  использоваться, тем самым предоставляя доступ для следующего метода. Объекты в куче доступны с любой точки программы,\n  в то время как стековая память не может быть доступна для других потоков.\n\n\u003e On-heap memory is memory in the Java heap, which is a region of memory managed by the garbage collector. Java objects\n\u003e reside in the heap. The heap can grow or shrink while the application runs. When the heap becomes full, garbage\n\u003e collection is performed: The JVM identifies the objects that are no longer being used (unreachable objects) and\n\u003e recycles their memory, making space for new allocations.\n\u003e Off-heap memory is memory outside the Java heap. To invoke a function or method from a different language such as C\n\u003e from a Java application, its arguments must be in off-heap memory. Unlike heap memory, off-heap memory is not subject\n\u003e to garbage collection when no longer needed. You can control how and when off-heap memory is deallocated.\n\n### Как в Java осуществляется поиск неиспользуемых объектов?\n\nВ Java процесс работы с памятью скрыт от программиста: JVM сама занимается выделением памяти и ее очисткой. Процесс\nочистки памяти называется Garbage Collection. Из названия следует, что GC занимается очисткой памяти, т.е. удаляет\nнеиспользуемые объекты из памяти. Весь процесс состоит из двух частей:\n\n* _mark_ – обход дерева объектов и поиск достижимых ссылок из корневых объектов (GC Roots).\n* _sweep_ – удаление неиспользуемых объектов.\n\n![Mark \u0026 Sweep](images/Mark%20and%20Sweep%20algorithm.png)\n\nПоиск мусора:\n\n1. Reference counting – у каждого объекта счетчик ссылок. Когда он равен нулю, объект считается мусором. В случае\n   обнаружения цикличных ссылок, объекты считаются недостижимыми, если на них не ссылаются никакие другие объекты.\n2. Tracing - объект считается не мусором, если до него можно добраться с корневых точек (GC Roots).\n\nКорневые точки (GC Roots):\n\n* Классы, загруженные системным ClassLoader'ом. Эти классы никогда не могут быть выгружены.\n* Активные потоки.\n* Локальные переменные, параметры методов.\n* Объекты, используемые в мониторе для синхронизации.\n* JNI (Java Native Interface).\n* Объекты, огражденные от сборки мусора самим JVM.\n\n### Примеры\n\n* OutOfMemoryError: Java heap space. \u003c-- рассмотрим этот класс ошибок.\n* OutOfMemoryError: Metaspace.\n* OutOfMemoryError: Requested array size exceeds VM limit.\n* OutOfMemoryError: Unable to create new native thread.\n* OutOfMemoryError: GC Overhead limit exceeded.\n\n#### Неверная реализация `equals` и `hashCode`\n\nПример: [EqualsAndHashCodeExample](src/main/java/ru/romanow/memory/leaks/EqualsAndHashCodeExample.java).\nЗапуск: `./gradlew runEqualsAndHashCodeExample`.\n\n#### Статические переменные\n\nПример: [StaticResourcesExample](src/main/java/ru/romanow/memory/leaks/StaticResourcesExample.java).\nЗапуск: `./gradlew runStaticResourcesExample`.\n\n#### Используем WeakReference\n\nПример: [StaticResourcesWeakReferenceExample](src/main/java/ru/romanow/memory/leaks/StaticResourcesWeakReferenceExample.java).\nЗапуск: `./gradlew runStaticResourcesWeakReferenceExample`.\n\nЗначение очищается, но ключ остается. Нужно использовать `java.util.WeakHashMap`.\n\n#### ThreadLocal переменные при использовании пула потоков\n\nПример: [ThreadLocalExample](src/main/java/ru/romanow/memory/leaks/ThreadLocalExample.java).\nЗапуск: `./gradlew runThreadLocalExample`.\n\n\u003e By definition, a reference to a ThreadLocal value is kept until the \"owning\" thread dies or if the ThreadLocal itself\n\u003e is no longer reachable.\n\n#### Пул строк (String interning)\n\nПример: [InternalStringsExample](src/main/java/ru/romanow/memory/leaks/InternalStringsExample.java).\nЗапуск: `./gradlew runInternalStringsExample`.\n\n\u003e Prior to Java 7 interned strings were allocated in PermGen space. This would become a garbage collector issue once\n\u003e your string is of no more use in application, since the interned string pool is a static member of the String class\n\u003e and will never be garbage collected. From Java 7 onward the interned strings are allocated on the Heap and are subject\n\u003e to garbage collection.\n\n#### TODO\n\n* Внутренние классы.\n* Как исправить статические члены класса с WeakReference. Использование WeakReference вместо WeakHashMap.\n\n### Как найти утечку памяти?\n\nКакого-то универсального алгоритма поиска утечки памяти нет, но вот список основных действий, которые нужно выполнить:\n\n1. Включить параметр JWM `-XX:+HeapDumpOnOutOfMemoryError` для получения heap dump при падении по OutOfMemoryError.\n   После этого загрузить результат в JProfiler или подобный инструмент и посмотреть какие **ваши** объекты занимают\n   много памяти (хотя не должны).\n2. Провести статический анализ кода на предмет утечек памяти (современные анализаторы умеют искать причины OOM).\n3. Возможно, просто нужно увеличить ресурсы приложения: возросла нагрузка или усложнились бизнес-операции.\n4. Если вы используете JNI (Java Native Interface) или другие средства нативного взаимодействия с памятью, то\n   постарайтесь уйти от этого. Возможно, лучше написать сервер на C++ и коммуницировать с ним через socket, чем напрямую\n   работать с памятью из Java (она для этого не приспособлена).\n\n```shell\n$ jps -l\n$ jstack \u003cPID\u003e \u003e threaddump.txt\n$ jhsdb jmap --heap --pid 62807    \n$ jmap -histo \u003cPID\u003e | head\n$ jmap -dump:format=b,file=heapdump.hprof \u003cPID\u003e\n```\n\n### Удаленная отладка\n\nДля подключения debugger'а нужно запустить с флагом:\n`-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8008`.\n\n```shell\n$ jdb -attach 8000\n$ stop at ru.romanow.memory.leaks.StaticResourcesExample:19\n$ print ru.romanow.memory.leaks.StaticResourcesExample.cache.size()\n$ locals\n$ step into\n$ step up\n$ resume\n$ clear ru.romanow.memory.leaks.StaticResourcesExample:19\n\n```\n\n### Выводы: список простых правил как бороться с утечками памяти\n\nKeep it simple.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromanow%2Fjava-memory-leaks-lecture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromanow%2Fjava-memory-leaks-lecture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromanow%2Fjava-memory-leaks-lecture/lists"}