Создание утечки памяти с Java

2891

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

Каким будет пример?

  • 5
    @bestsss: я забыл о File.deleteOnExit() , это здорово. Если вы опубликуете это как ответ, я добавлю это.
  • 2
    @ Даниил, я думаю, я могу добавить как минимум 10 дополнительных странных случаев утечек памяти (моя вторая специальность чувствует отслеживание таких проблем)
Показать ещё 30 комментариев
Теги:
memory-leaks
memory

54 ответа

2222
Лучший ответ

Здесь хороший способ создать настоящую утечку памяти (объекты, недоступные при запуске кода, но все еще хранящиеся в памяти) в чистой Java:

  • Приложение создает длинный поток (или использует пул потоков, чтобы течь еще быстрее).
  • Поток загружает класс через (необязательно настраиваемый) ClassLoader.
  • Класс выделяет большой фрагмент памяти (например, new byte[1000000]), сохраняет в нем сильную ссылку в статическом поле и затем сохраняет ссылку на себя в ThreadLocal. Выделение дополнительной памяти необязательно (утечка экземпляра класса достаточно), но это сделает работу с утечкой намного быстрее.
  • Нить очищает все ссылки на пользовательский класс или загрузчик ClassLoader, из которого он был загружен.
  • Повтор.

Это работает, потому что ThreadLocal сохраняет ссылку на объект, который сохраняет ссылку на свой класс, который, в свою очередь, ссылается на свой ClassLoader. ClassLoader, в свою очередь, сохраняет ссылку на все загруженные классы.

(Это было хуже во многих реализациях JVM, особенно до Java 7, потому что Classes и ClassLoaders были выделены прямо в permgen и вообще не были GC'd. Однако независимо от того, как JVM обрабатывает разгрузку классов, ThreadLocal будет все еще препятствует возврату объекта класса.)

Вариант этого шаблона заключается в том, почему контейнеры приложений (например, Tomcat) могут утечка памяти, например, сита, если вы часто передислоцируете приложения, которые каким-либо образом используют ThreadLocals. (Так как контейнер приложения использует потоки, как описано, и каждый раз, когда вы повторно развертываете приложение, используется новый ClassLoader.)

Обновить. Поскольку многие люди продолжают его просить, вот пример кода, который показывает это поведение в действии.

  • 156
    +1 Утечки ClassLoader являются одними из наиболее болезненных утечек памяти в мире JEE, часто вызываемых сторонними библиотеками, которые преобразуют данные (BeanUtils, кодеки XML / JSON). Это может произойти, когда библиотека загружена вне корневого загрузчика классов вашего приложения, но содержит ссылки на ваши классы (например, путем кэширования). Когда вы удаляете / повторно развертываете свое приложение, JVM не может собрать мусор как загрузчик классов приложения (и, следовательно, все классы, загруженные им), поэтому при повторном развертывании сервер приложений в конечном итоге не работает. Если вам повезет, вы получите подсказку с ClassCastException, zxyAbc не может быть приведен к zxyAbc.
  • 6
    tomcat использует трюки и обнуляет ВСЕ статические переменные во ВСЕХ загруженных классах, хотя tomcat имеет много данных и плохое кодирование (требуется некоторое время и отправляет исправления), а также ошеломляющий ConcurrentLinkedQueue в качестве кеша для внутренних (маленьких) объектов, настолько маленький, что даже ConcurrentLinkedQueue.Node занимает больше памяти.
Показать ещё 32 комментария
1187

Статическое поле, содержащее ссылку на объект [esp final field]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Вызов String.intern() для длинной строки

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(Незакрытые) открытые потоки (файл, сеть и т.д.)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Незакрытые соединения

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Области, недоступные из сборщика мусора JVM, такие как память, выделенная с помощью собственных методов

В веб-приложениях некоторые объекты хранятся в области приложения до тех пор, пока приложение не будет явно остановлено или удалено.

getServletContext().setAttribute("SOME_MAP", map);

Неправильные или неподходящие параметры JVM, такие как параметр noclassgc в IBM JDK, который предотвращает неиспользуемую сборку мусора классов

Смотрите настройки IBM jdk.

  • 169
    Я не согласен с тем, что контекст и атрибуты сеанса являются «утечками». Это просто долгоживущие переменные. А статическое конечное поле более или менее просто константа. Возможно, следует избегать больших констант, но я не думаю, что было бы справедливо называть это утечкой памяти.
  • 73
    (Незакрытые) открытые потоки (файл, сеть и т. Д.) , Не пропускают по-настоящему, во время финализации (что будет после следующего цикла GC) close () будет запланировано ( close() обычно не вызывается в потоке финализатора, так как может быть операция блокировки). Это плохая практика - не закрывать, но это не приводит к утечке. Незакрытый java.sql. Соединение такое же.
Показать ещё 19 комментариев
426

Простая задача - использовать HashSet с неправильным (или несуществующим) hashCode() или equals(), а затем продолжать добавлять "дубликаты". Вместо того, чтобы игнорировать дубликаты, как это должно быть, набор будет только расти, и вы не сможете их удалить.

Если вы хотите, чтобы эти плохие ключи/элементы зависали, вы можете использовать статическое поле типа

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
  • 0
    @delnan, недостаточно предоставления hashCode () / equals ().
  • 60
    На самом деле, вы можете удалить элементы из HashSet, даже если класс элемента получает hashCode и равен неправильному; просто получите итератор для набора и используйте его метод remove, поскольку итератор фактически работает с самими базовыми записями, а не с элементами. (Обратите внимание , что невыполненный хэш - код / равно не достаточно , чтобы вызвать утечку, по умолчанию реализовать простой идентификатор объекта , и поэтому вы можете получить элементы и удалить их обычно.)
Показать ещё 15 комментариев
266

Ниже будет неочевидный случай, когда Java утечки, помимо стандартного случая забытых слушателей, статических ссылок, поддельных/модифицируемых ключей в хэшмапах или просто потоков, застрявших без каких-либо шансов положить конец жизненному циклу.

  • File.deleteOnExit() - всегда теряет строку, , если строка является подстрокой, утечка еще хуже (скрывается char []) - в подстроке Java 7 копирует char[], поэтому более поздняя версия не применяется; @Даниэль, не нужны голоса, тем не менее.

Я сосредоточусь на потоках, чтобы в основном показать опасность неуправляемых потоков, не желая даже прикасаться к качелям.

  • Runtime.addShutdownHook и не удалять... а затем даже с removeShutdownHook из-за ошибки в классе ThreadGroup относительно нерасширенных потоков он может не собираться, эффективно утечка ThreadGroup. JGroup имеет утечку в GossipRouter.

  • Создание, но не запуск, Thread переходит в ту же категорию, что и выше.

  • Создание потока наследует ContextClassLoader и AccessControlContext, плюс ThreadGroup и любые InheritedThreadLocal, все эти ссылки являются потенциальными утечками, а также все классы, загружаемые загрузчиком классов и всеми статическими ссылками, и ja-ja. Эффект особенно заметен со всей инфраструктурой j.u.c.Executor, которая имеет супер простой интерфейс ThreadFactory, но большинство разработчиков не имеют понятия скрытой опасности. Также много библиотек запускают потоки по запросу (слишком много отраслевых популярных библиотек).

  • ThreadLocal кеши; во многих случаях это зло. Я уверен, что все видели довольно много простых кешей на основе ThreadLocal, а также плохие новости: если поток продолжает больше, чем ожидалось, жизнь в контексте ClassLoader, это чистая приятная небольшая утечка. Не используйте кэши ThreadLocal, если это действительно необходимо.

  • Вызов ThreadGroup.destroy(), когда ThreadGroup не имеет потоков, но он по-прежнему сохраняет дочерние потоковые группы. Плохая утечка, которая предотвратит удаление ThreadGroup из родительского элемента, но все дети становятся un-enumerateable.

  • Использование WeakHashMap, а значение (in) напрямую ссылается на ключ. Это трудно найти без кучи кучи. Это относится ко всем расширенным Weak/SoftReference, которые могли бы сохранить верную ссылку на охраняемый объект.

  • Используя java.net.URL с протоколом HTTP (S) и загрузив ресурс из (!). Этот особенный, KeepAliveCache создает новый поток в системе ThreadGroup, который утечки текущего загрузчика потока контекста потока. Поток создается при первом запросе, когда нет ни одного живого потока, так что вам может повезти или просто утечка. Утечка уже исправлена ​​в Java 7, а код, который создает поток, правильно удаляет загрузчик классов. Есть еще несколько случаев ( как ImageFetcher, также исправлено) создания похожих потоков.

  • Использование InflaterInputStream передачи new java.util.zip.Inflater() в конструкторе (например, PNGImageDecoder) и не вызов end() надувателя. Ну, если вы передадите конструктору только с new, нет шансов... И да, вызов close() в потоке не закрывает надув, если он вручную передается как параметр конструктора. Это не настоящая утечка, так как она будет выпущена финализатором... когда она сочтет это необходимым. До того момента, когда он так сильно поедает родную память, он может заставить Linux oom_killer безнаказанно убить процесс. Основная проблема заключается в том, что финализация на Java очень ненадежна, а G1 ухудшилась до 7.0.2. Мораль истории: как можно скорее выпустите родные ресурсы; финализатор слишком плох.

  • В том же случае с java.util.zip.Deflater. Это намного хуже, поскольку Deflater является голодной памятью на Java, т.е. Всегда использует 15 и nbsp; бит (макс.) И 8 уровни памяти (9 - макс.), Выделяя несколько сотен КБ встроенной памяти. К счастью, Deflater широко не используется, и, насколько мне известно, JDK не содержит злоупотреблений. Всегда вызывайте end(), если вы вручную создаете Deflater или Inflater. Лучшая часть последних двух: вы не можете найти их с помощью обычных инструментов профилирования.

(Я могу добавить еще несколько отрывков времени, с которыми я столкнулся по запросу.)

Удачи и оставайтесь в безопасности; утечки злые!

  • 20
    Creating but not starting a Thread... Да, я был сильно укушен этим несколько веков назад! (Java 1.3)
  • 0
    @leonbloy, до того как стало еще хуже, поскольку поток был добавлен прямо в группу потоков, отсутствие запуска означало очень серьезную утечку. Не только увеличивает количество unstarted но и предотвращает разрушение группы потоков (меньшее зло, но все же утечка)
Показать ещё 2 комментария
179

Большинство примеров здесь "слишком сложны". Это крайние случаи. В этих примерах программист допустил ошибку (например, не переопределяет equals/hashcode) или был укушен угловым случаем JVM/JAVA (загрузка класса со статическим...). Я думаю, что это не тот пример, который хочет интервьюер, или даже самый распространенный случай.

Но есть действительно более простые случаи утечки памяти. Сборщик мусора освобождает только то, на что больше нет ссылок. Мы, как разработчики Java, не заботимся о памяти. Мы распределяем его по мере необходимости и позволяем автоматически его освобождать. Хорошо.

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

Конечно, всевозможные слушатели (например, слушатели пользовательского интерфейса), кеши или любое долгоживущее общее состояние имеют тенденцию вызывать утечку памяти, если не обрабатываются должным образом. Следует понимать, что это не случай Java или проблема с сборщиком мусора. Это проблема дизайна. Мы проектируем, что мы добавляем слушателя к долгоживущему объекту, но мы не удаляем слушателя, когда он больше не нужен. Мы кешируем объекты, но у нас нет стратегии их удаления из кеша.

Возможно, у нас есть сложный граф, в котором хранится предыдущее состояние, необходимое для вычислений. Но предыдущее состояние само по себе связано с состоянием до и так далее.

Как мы должны закрыть соединения или файлы SQL. Нам нужно установить правильные ссылки на нуль и удалить элементы из коллекции. У нас будут правильные стратегии кэширования (максимальный объем памяти, количество элементов или таймеры). Все объекты, позволяющие уведомлять слушателя, должны предоставлять метод addListener и removeListener. И когда эти уведомители больше не используются, они должны очистить свой список слушателей.

Утечка памяти действительно возможна и вполне предсказуема. Нет необходимости в специальных языковых функциях или угловых случаях. Утечки памяти - это либо показатель того, что чего-то не хватает, либо даже проблемы с дизайном.

  • 18
    Я нахожу забавным, что в других ответах люди ищут эти крайние случаи и уловки и, кажется, полностью упускают суть. Они могут просто показать код, который хранит бесполезные ссылки на объекты, которые никогда не будут использоваться снова, и никогда не удалять эти ссылки; Можно сказать, что эти случаи не являются «истинными» утечками памяти, потому что все еще существуют ссылки на эти объекты, но если программа никогда не использует эти ссылки снова и никогда не удаляет их, это полностью эквивалентно (и так плохо, как) a » Истинная утечка памяти ".
  • 0
    @ Николас Буске: «Утечка памяти действительно возможна» Большое спасибо. +15 голосов. Ницца. Я кричал здесь за то, что констатировал этот факт в качестве вопроса о языке Go: stackoverflow.com/questions/4400311 Этот вопрос по-прежнему имеет отрицательные отзывы :(
Показать ещё 3 комментария
155

Ответ полностью зависит от того, что, по мнению интервьюера, они спрашивали.

Можно ли на практике сделать утечку Java? Конечно, это так, и в других ответах есть много примеров.

Но есть несколько мета-вопросов, которые могли быть заданы?

  • Является ли теоретически "идеальная" реализация Java уязвимой для утечек?
  • Может ли кандидат понять разницу между теорией и реальностью?
  • Помогает ли кандидат понять, как работает сбор мусора?
  • Или как сбор мусора должен работать в идеальном случае?
  • Знают ли они, что они могут вызывать другие языки через собственные интерфейсы?
  • Знают ли они, что утечка памяти на этих других языках?
  • Может ли кандидат знать, что такое управление памятью, и что происходит за сценой в Java?

Я читаю ваш мета-вопрос как "Какой ответ я мог бы использовать в этой ситуации интервью". И, следовательно, я собираюсь сосредоточиться на навыках собеседования вместо Java. Я верю, что вы с большей вероятностью повторите ситуацию, когда не знаете ответа на вопрос в интервью, чем вы должны быть в нужном месте, чтобы знать, как сделать утечку Java. Так что, надеюсь, это поможет.

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

  • 21
    Всякий раз, когда я задаю этот вопрос, я ищу довольно простой ответ - продолжайте наращивать очередь, не закрывайте, наконец, дб и т. Д., А не нечетные подробности загрузчика классов / потоков, подразумевает, что они понимают, что gc может и не может сделать для вас. Зависит от работы, с которой вы берете интервью.
  • 0
    Пожалуйста, посмотрите на мой вопрос, спасибо stackoverflow.com/questions/31108772/…
119

Ниже приведен довольно бессмысленный пример, если вы не понимаете JDBC. Или, по крайней мере, как JDBC ожидает, что разработчик закроет экземпляры Connection, Statement и ResultSet перед тем, как отбросить их или потерять ссылки на них, вместо того, чтобы полагаться на реализацию finalize.

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

Проблема с вышеизложенным заключается в том, что объект Connection не закрыт, и, следовательно, физическое соединение останется открытым, пока сборщик мусора не появится и не увидит, что он недоступен. GC вызовет метод finalize, но есть драйверы JDBC, которые не реализуют finalize, по крайней мере, не так, как реализовано Connection.close. Результатом является то, что, хотя память будет восстановлена ​​из-за сбоя недоступных объектов, ресурсы (включая память), связанные с объектом Connection, могут просто не быть исправлены.

В таком случае, когда метод Connection finalize не очищает все, на самом деле можно обнаружить, что физическое соединение с сервером базы данных будет длиться несколько циклов сбора мусора, пока сервер базы данных в конце концов не выяснит, что соединение не является живым (если оно есть) и должно быть закрыто.

Даже если драйвер JDBC должен был реализовать finalize, во время финализации можно исключить исключения. Полученное поведение состоит в том, что любая память, связанная с теперь "бездействующим" объектом, не будет исправлена, поскольку finalize гарантированно будет вызываться только один раз.

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

Есть еще много примеров, которые вы можете вызвать, например,

  • Управление экземпляром List, в котором вы добавляете только этот список и не удаляете его (хотя вам нужно избавиться от ненужных элементов) или
  • Открытие Socket или File s, но не закрытие их, когда они больше не нужны (аналогично приведенному выше примеру с классом Connection).
  • Не выгружать синглеты при подаче приложения Java EE. По-видимому, Classloader, загрузивший singleton-класс, сохранит ссылку на класс, и, следовательно, экземпляр singleton никогда не будет собран. Когда развертывается новый экземпляр приложения, обычно создается новый загрузчик классов, и прежний загрузчик классов будет продолжать существовать из-за синглета.
  • 92
    Вы достигнете максимального предела открытого соединения прежде, чем достигнете пределов памяти. Не спрашивайте меня, почему я знаю ...
  • 0
    Драйвер Oracle JDBC известен тем, что делает это.
Показать ещё 4 комментария
107

Вероятно, одним из простейших примеров потенциальной утечки памяти и как ее избежать является реализация ArrayList.remove(int):

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

Если вы сами его реализуете, подумали бы вы очистить элемент массива, который больше не используется (elementData[--size] = null)? Эта ссылка может сохранить живой объект...

  • 3
    'elementData [- size] = null;' делает очистку, я думаю ...
  • 5
    И где здесь утечка памяти?
Показать ещё 3 комментария
65

При каждом обращении к объектам, которые вам больше не нужны, у вас есть утечка памяти. См. Обработка утечек памяти в Java-программах на примерах того, как утечка памяти проявляется на Java и что вы можете с ней делать.

  • 12
    Я не верю, что это «утечка». Это ошибка, и это по дизайну программы и языка. Утечка - это объект, который висит без каких-либо ссылок на него.
  • 27
    @ Mehrdad: Это только одно узкое определение, которое не в полной мере относится ко всем языкам. Я бы сказал, что любая утечка памяти - это ошибка, вызванная плохим дизайном программы.
Показать ещё 17 комментариев
47

Вы можете сделать утечку памяти с помощью класса sun.misc.Unsafe. Фактически этот класс обслуживания используется в разных стандартных классах (например, в классах java.nio). Невозможно создать экземпляр этого класса напрямую, но вы можете использовать отражение для этого.

Код не компилируется в Eclipse IDE - скомпилируйте его с помощью команды javac (во время компиляции вы получите предупреждения)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}
  • 0
    Выделенная память невидима для сборщика мусора
  • 3
    Выделенная память также не принадлежит Java.
Показать ещё 5 комментариев
41

Я могу скопировать свой ответ отсюда: Самый простой способ вызвать утечку памяти в Java?

"Утечка памяти в компьютерной науке (или утечке в этом контексте) возникает, когда компьютерная программа потребляет память, но не может ее вернуть в операционную систему". (Википедия)

Простой ответ: вы не можете. Java выполняет автоматическое управление памятью и освобождает ресурсы, которые вам не нужны. Вы не можете остановить это. Он ВСЕГДА сможет освободить ресурсы. В программах с ручным управлением памятью это другое. Вы не можете получить некоторую память в C, используя malloc(). Чтобы освободить память, вам понадобится указатель, возвращаемый malloc, и вызов на него free(). Но если у вас больше нет указателя (перезаписан или превышен срок службы), то вы, к сожалению, не можете освободить эту память, и, следовательно, у вас есть утечка памяти.

Все остальные ответы до сих пор в моем определении не являются утечками памяти. Все они стремятся наполнить память бессмысленными вещами очень быстро. Но в любое время вы все равно можете разыменовать созданные вами объекты и освободить память → NO LEAK. ответ acconrad довольно близок, хотя, как я должен признать, так как его решение эффективно просто "выбить" сборщик мусора, заставив его в бесконечном цикле).

Долгий ответ: вы можете получить утечку памяти, написав библиотеку для Java с помощью JNI, которая может иметь ручное управление памятью и, следовательно, иметь утечки памяти. Если вы вызовете эту библиотеку, ваш java-процесс будет утечка памяти. Или у вас могут быть ошибки в JVM, так что JVM теряет память. Вероятно, в JVM есть ошибки, возможно, даже некоторые из них известны, поскольку сбор мусора не является тривиальным, но тогда он все еще является ошибкой. По дизайну это невозможно. Возможно, вы запрашиваете какой-то Java-код, который возникает из-за такой ошибки. Извините, я не знаю одного, и в любом случае это может не быть ошибкой в ​​следующей версии Java.

  • 9
    Это чрезвычайно ограниченное (и не очень полезное) определение утечек памяти. Единственное определение, имеющее смысл для практических целей, - «утечка памяти - это любое состояние, при котором программа продолжает удерживать память, выделенную после того, как данные, в которых она хранится, больше не нужны».
  • 0
    Упомянутый ответ acconrad был удален?
Показать ещё 3 комментария
36

Здесь простой/зловещий через http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29.

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

Поскольку подстрока относится к внутреннему представлению оригинальной, гораздо более длинной строки, оригинал остается в памяти. Таким образом, если у вас есть StringLeaker в игре, у вас есть целая оригинальная строка в памяти, даже если вы можете подумать, что вы просто держитесь за односимвольную строку.

Способ избежать нежелательной ссылки на исходную строку состоит в том, чтобы сделать что-то вроде этого:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

При добавлении плохой ситуации вы также можете .intern() подстроку:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

При этом сохраняется как исходная длинная строка, так и производная подстрока в памяти даже после того, как экземпляр StringLeaker был отброшен.

  • 4
    Я бы не назвал это утечкой памяти, как таковой . Когда muchSmallerString освобождается (поскольку объект StringLeaker уничтожен), длинная строка также будет освобождена. То, что я называю утечкой памяти, - это память, которая никогда не может быть освобождена в этом экземпляре JVM. Однако вы показали, как освободить память: this.muchSmallerString=new String(this.muchSmallerString) . С настоящей утечкой памяти ничего не поделаешь.
  • 2
    @rds, это справедливо. Случай без intern может быть скорее «неожиданностью памяти», чем «утечкой памяти». .intern() , однако, подстрока, безусловно, создает ситуацию, когда ссылка на более длинную строку сохраняется и не может быть освобождена.
Показать ещё 2 комментария
36

Общим примером этого в GUI-коде является создание виджета/компонента и добавление слушателя к некоторому объекту с статическими/прикладными областями, а затем не удаление слушателя при уничтожении виджета. Вы получаете не только утечку памяти, но и производительность, так как когда вы слушаете события пожаров, все ваши старые слушатели также вызываются.

34

Возьмите любое веб-приложение, работающее в любом контейнере сервлетов (Tomcat, Jetty, Glassfish, что угодно...). Обновите приложение 10 или 20 раз подряд (этого может быть достаточно, чтобы просто коснуться WAR в каталоге autodeploy сервера.

Если кто-либо действительно не протестировал это, есть вероятность, что вы получите OutOfMemoryError после нескольких повторных развертываний, потому что приложение не позаботится о том, чтобы очистить себя. Вы можете даже найти ошибку на своем сервере с этим тестом.

Проблема в том, что срок службы контейнера больше, чем срок службы вашего приложения. Вы должны убедиться, что все ссылки, которые контейнер может иметь на объекты или классы вашего приложения, могут быть собраны в мусор.

Если есть только одна ссылка, сохранившаяся без развертывания вашего веб-приложения, соответствующий загрузчик классов и, следовательно, все классы вашего веб-приложения не могут быть собраны в мусор.

Потоки, запущенные вашим приложением, переменные ThreadLocal, добавление журналов являются некоторыми из обычных подозреваемых, которые вызывают утечку загрузчика.

  • 1
    Это не из-за утечки памяти, а потому, что загрузчик классов не выгружает предыдущий набор классов. Поэтому не рекомендуется повторно развертывать сервер приложений без перезапуска сервера (не физического компьютера, а сервера приложений). Я видел ту же проблему с WebSphere.
31

Может быть, используя внешний собственный код через JNI?

С чистой Java это почти невозможно.

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

  • 20
    Это зависит от определения «утечки памяти». Если «память хранится, но больше не нужна», то это легко сделать в Java. Если это «память, которая выделена, но вообще не доступна для кода», то это становится немного сложнее.
  • 0
    @ Йоахим Зауэр - я имел в виду второй тип. Первый довольно легко сделать :)
Показать ещё 2 комментария
30

У меня была хорошая "утечка памяти" по отношению к PermGen и синтаксическому анализу XML один раз. Используемый нами синтаксический анализатор XML (я не помню, какой он был) использовал имена тегов String.intern(), чтобы ускорить сравнение. У одного из наших клиентов была отличная идея хранить значения данных не в атрибутах или тексте XML, а в качестве тэгов, поэтому у нас был документ вроде:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

Фактически, они не использовали числа, а более длинные текстовые идентификаторы (около 20 символов), которые были уникальными и приходили со скоростью 10-15 миллионов в день. Это составляет 200 МБ мусора в день, который больше никогда не нужен, и никогда не GCed (так как он находится в PermGen). У нас был permgen установлен на 512 Мб, поэтому потребовалось около двух дней для исключения из памяти (OOME), чтобы прибыть...

  • 4
    Просто чтобы придираться к вашему примеру кода: я думаю, что числа (или строки, начинающиеся с цифр) не допускаются в качестве имен элементов в XML.
  • 1
    Мне кажется, ты прав. Это было просто для демонстрации.
Показать ещё 1 комментарий
23

Какая утечка памяти:

  • Это вызвано ошибкой или плохим дизайном.
  • Это пустая трата памяти.
  • Со временем ухудшается.
  • Сборщик мусора не может его очистить.

Типичный пример:

Кэш объектов - хорошая отправная точка, чтобы повредить вещи.

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

Ваш кеш растет и растет. И довольно скоро вся база данных всасывается в память. В лучшем дизайне используется LRUMap (только хранит недавно использованные объекты в кеше).

Конечно, вы можете сделать вещи намного сложнее:

  • с помощью конструкций ThreadLocal.
  • добавление дополнительных сложных деревьев ссылок.
  • или утечки, вызванные сторонними библиотеками.

Что часто происходит:

Если этот объект Info имеет ссылки на другие объекты, которые снова имеют ссылки на другие объекты. В некотором смысле вы также можете считать, что это какая-то утечка памяти (вызвана плохим дизайном).

23

Недавно я столкнулся с ситуацией утечки памяти, вызванной способом log4j.

Log4j имеет этот механизм под названием Вложенный диагностический контекст (NDC), который является инструментом для различения данных с чередованием журналов из разных источников. Гранулярность, с которой работает NDC, - это потоки, поэтому она различает выходные данные из разных потоков отдельно.

Для хранения тегов, связанных с потоком, класс log4j NDC использует Hashtable, который связан с самим объектом Thread (в отличие от идентификатора потока), и, таким образом, до тех пор, пока тег NDC не останется в памяти всех объектов, объекта потока также остаются в памяти. В нашем веб-приложении мы используем NDC для тегов logoutputs с идентификатором запроса, чтобы отличать журналы от одного запроса отдельно. Контейнер, который связывает тег NDC с потоком, также удаляет его, возвращая ответ от запроса. Проблема возникла, когда во время обработки запроса был создан дочерний поток, что-то вроде следующего кода:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

Таким образом, контекст NDC был связан с встроенным потоком, который был порожден. Объект потока, который был ключом для этого контекста NDC, является встроенным потоком, который имеет огромный объект LIST, зависающий от него. Поэтому даже после того, как поток закончил делать то, что он делал, ссылка на огромный список была сохранена в контексте HDC HDC, что вызвало утечку памяти.

  • 0
    Это отстой. Вы должны проверить эту библиотеку журналов, которая выделяет нулевую память при регистрации в файле: mentalog.soliveirajr.com
  • 0
    НЦД хранятся в ThreadLocal.
Показать ещё 1 комментарий
20

Я думал, что было интересно, что никто не использовал примеры внутреннего класса. Если у вас есть внутренний класс; он по своей сути поддерживает ссылку на содержащий класс. Конечно, это не технически утечка памяти, потому что Java в конечном итоге очистит ее; но это может привести к тому, что классы будут работать дольше, чем ожидалось.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Теперь, если вы вызываете Example1 и получаете пример 2, отбрасывающий Example1, у вас по-прежнему будет ссылка на объект Example1.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

Я также слышал слухи, что если у вас есть переменная, которая существует дольше определенного времени; Java предполагает, что он всегда будет существовать и на самом деле никогда не будет пытаться его очистить, если он больше не может быть достигнут в коде. Но это совершенно непроверено.

  • 2
    внутренние классы редко являются проблемой. Это простой случай, и его очень легко обнаружить. Слух тоже только слух.
  • 1
    «Слух» звучит как кто-то наполовину прочитавший о том, как работает GC поколений. Долгоживущие, но теперь недостижимые объекты действительно могут оставаться на месте и некоторое время занимать пространство, потому что JVM продвигала их из подрастающего поколения, чтобы она могла перестать проверять их при каждом проходе. По замыслу, они будут обходить обходные проходы «очистить мои 5000 временных строк». Но они не бессмертны. Они по-прежнему имеют право на сбор, и, если виртуальная машина привязана к ОЗУ, она в конечном итоге запустит полную очистку GC и вернет эту память.
19

Создайте статическую карту и продолжайте добавлять к ней жесткие ссылки. Те никогда не будут GC'd.

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
  • 0
    Не могли бы вы привести пример кода?
  • 82
    Как это утечка? Он делает именно то, что вы просите. Если это утечка, создание и хранение объектов в любом месте - это утечка.
Показать ещё 6 комментариев
17

Интервьюер, вероятно, искал круговую ссылку, такую ​​как код ниже (который, кстати, только утечка памяти в очень старых JVM, которые использовали подсчет ссылок, что больше не так). Но это довольно неопределенный вопрос, поэтому это прекрасная возможность продемонстрировать ваше понимание управления памятью JVM.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Затем вы можете объяснить, что при подсчете ссылок вышеуказанный код будет утечка памяти. Но большинство современных JVM больше не используют подсчет ссылок, большинство используют сборщик мусора, который фактически собирает эту память.

Далее вы можете объяснить создание объекта с базовым исходным ресурсом, например:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Затем вы можете объяснить, что это технически утечка памяти, но на самом деле утечка вызвана собственным кодом в JVM, который выделяет основные ресурсы, которые не были освобождены вашим кодом Java.

В конце дня, с современной JVM, вам нужно написать некоторый Java-код, который выделяет собственный ресурс вне обычной области осведомленности о JVM.

16

Вы можете создать утечку движущейся памяти, создав новый экземпляр класса в этом методе finalize. Бонусные очки, если финализатор создает несколько экземпляров. Здесь простая программа, которая утечки всей кучи за несколько секунд и несколько минут в зависимости от вашего размера кучи:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}
16

Каждый всегда забывает маршрут собственного кода. Здесь простая формула для утечки:

  • Объявить собственный метод.
  • В собственном методе вызовите malloc. Не вызывайте free.
  • Вызвать собственный метод.

Помните, что выделение памяти в собственном коде происходит из кучи JVM.

  • 0
    Основано на реальных событиях.
15

Недавно я столкнулся с более тонким видом утечки ресурсов. Мы открываем ресурсы через загрузчик классов getResourceAsStream, и оказалось, что обработчики входного потока не были закрыты.

Эм, можно сказать, какой идиот.

Ну, что делает интересным это: таким образом, вы можете пропустить кучу памяти лежащего в основе процесса, а не из кучи JVM.

Все, что вам нужно, это файл jar с файлом, внутри которого будет ссылаться код Java. Чем больше файл jar, тем быстрее выделяется более быстрая память.

Вы можете легко создать такую ​​банку со следующим классом:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

Просто вставьте в файл BigJarCreator.java, скомпилируйте и запустите его из командной строки:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: вы найдете архив jar в своем текущем рабочем каталоге с двумя файлами внутри.

Создайте второй класс:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

Этот класс в основном ничего не делает, но создает неопубликованные объекты InputStream. Эти объекты будут собирать мусор немедленно и, таким образом, не вносить вклад в размер кучи. Для нашего примера важно загрузить существующий ресурс из файла jar, и размер имеет значение здесь!

Если вы сомневаетесь, попробуйте скомпилировать и запустить класс выше, но обязательно выберите подходящий размер кучи (2 МБ):

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

Здесь вы не столкнетесь с ошибкой OOM, так как ссылки не поддерживаются, приложение будет работать независимо от того, насколько большой вы выбрали Итерации в приведенном выше примере. Потребление памяти вашего процесса (видимое в верхнем (RES/RSS) или проводнике процессов) растет, если приложение не получает команду wait. В вышеприведенной настройке он выделяет около 150 МБ в памяти.

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

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

и ваш процесс не будет превышать 35 МБ, независимо от количества итераций.

Довольно просто и удивительно.

15

Я не думаю, что кто-то еще это сказал: вы можете воскресить объект, переопределив метод finalize() таким образом, чтобы finalize() хранит ссылку на это где-то. Сборщик мусора будет вызываться только один раз на объекте, после чего объект никогда не будет уничтожен.

  • 10
    Это неправда. finalize() не будет вызван, но объект будет собран, как только не будет больше ссылок. Сборщик мусора тоже не называется.
  • 1
    Этот ответ вводит в заблуждение: метод finalize() может быть вызван только один раз JVM, но это не означает, что его нельзя собрать повторно, если объект воскресен, а затем снова разыменован. Если в методе finalize() есть код закрытия ресурса, этот код больше не будет запускаться, это может привести к утечке памяти.
14

Как и многие люди, утечки ресурсов довольно легко вызвать - например, примеры JDBC. Фактические утечки памяти немного сложнее - особенно если вы не полагаетесь на разбитые биты JVM, чтобы сделать это для вас...

Идеи создания объектов, которые имеют очень большой размер, а затем не могут получить к ним доступ, также не являются настоящими утечками памяти. Если ничто не может получить к нему доступ, тогда будет собран мусор, и если что-то сможет получить к нему доступ, то это не утечка...

Один из способов, который раньше работал, - и я не знаю, все еще ли это, - иметь трехмерную круговую цепочку. Так как в объекте A имеется ссылка на объект B, объект B имеет ссылку на объект C, а объект C имеет ссылку на объект A. GC был достаточно умен, чтобы знать, что две глубокие цепи - как в ↔ B - можно безопасно собрать, если A и B недоступны ничем другим, но не могут обрабатывать трехстороннюю цепочку...

  • 7
    Некоторое время не было. Современные GC знают, как обрабатывать циклические ссылки.
11

Нитки не собираются до тех пор, пока они не прекратятся. Они служат roots сборкой мусора. Они являются одним из немногих объектов, которые не будут восстановлены просто, забыв о них или очистив ссылки на них.

Рассмотрим: основной шаблон для завершения рабочего потока - это установить некоторую переменную условия, видимую нитью. Поток может периодически проверять переменную и использовать это как сигнал для завершения. Если переменная не объявлена ​​ volatile, тогда изменение в переменной может не отображаться нитью, поэтому она не будет знать, что она завершена. Или представьте, если некоторые потоки хотят обновить общий объект, но тупик при попытке заблокировать его.

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

В качестве примера игрушки:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Вызовите System.gc() все, что вам нравится, но объект, переданный в leakMe, никогда не умрет.

(* изм *)

  • 1
    Это не утечка, код застрял, но он все еще находится в области видимости.
  • 1
    @ Spidey Ничто не "застряло". Вызывающий метод возвращается быстро, и переданный объект никогда не будет возвращен. Это именно утечка.
Показать ещё 16 комментариев
11

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

public class ServiceFactory {

private Map<String, Service> services;

private static ServiceFactory singleton;

private ServiceFactory() {
    services = new HashMap<String, Service>();
}

public static synchronized ServiceFactory getDefault() {

    if (singleton == null) {
        singleton = new ServiceFactory();
    }
    return singleton;
}

public void addService(String name, Service serv) {
    services.put(name, serv);
}

public void removeService(String name) {
    services.remove(name);
}

public Service getService(String name, Service serv) {
    return services.get(name);
}

// the problematic api, which expose the map.
//and user can do quite a lot of thing from this api.
//for example, create service reference and forget to dispose or set it null
//in all this is a dangerous api, and should not expose 
public Map<String, Service> getAllServices() {
    return services;
}

}

// resource class is a heavy class
class Service {

}
10

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

Например, используя переменные ThreadLocal в Servlets для связи с другими веб-компонентами, создавая потоки, создаваемые контейнером, и поддерживая незанятые в пуле. Переменные ThreadLocal, если не правильно очищены, будут жить там, пока, возможно, один и тот же веб-компонент не перезапишет их значения.

Конечно, после идентификации проблема может быть решена легко.

9

Другой способ создать потенциально большие утечки памяти - хранить ссылки на Map.Entry<K,V> TreeMap.

Трудно понять, почему это применимо только к TreeMap, но, глядя на реализацию, причина может быть в следующем: TreeMap.Entry хранит ссылки на своих братьев и сестер, поэтому, если TreeMap готов к сбору, но некоторые другие классы содержат ссылка на любой из его Map.Entry, тогда вся карта будет сохранена в памяти.


Реальный сценарий:

Представьте, что у вас есть запрос TreeMap данных, который возвращает большую структуру данных TreeMap. Люди обычно используют TreeMap поскольку порядок вставки элементов сохраняется.

public static Map<String, Integer> pseudoQueryDatabase();

Если запрос вызывался много раз, и для каждого запроса (то есть для каждой возвращаемой Map) вы сохраняете Entry где-нибудь, память постоянно будет расти.

Рассмотрим следующий класс-оболочку:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

Заявка:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

После каждого pseudoQueryDatabase() экземпляры map должны быть готовы к сбору, но этого не произойдет, поскольку хотя бы одна Entry хранится где-то еще.

В зависимости от настроек jvm, приложение может OutOfMemoryError на ранней стадии из-за OutOfMemoryError.

Из этого графика visualvm как растет память.

Изображение 1305

То же самое не происходит с хешированной структурой данных (HashMap).

Это график при использовании HashMap.

Изображение 1306

Решение? Просто сохраните ключ/значение (как вы, вероятно, уже сделали) вместо сохранения Map.Entry.


Я написал более обширный тест здесь.

9

Недавно я установил новый объект GC и Image, но забыл вызвать метод dispose().

GC javadoc snippet:

Код приложения должен явно ссылаться на метод GC.dispose(), чтобы освободить ресурсы операционной системы, управляемые каждым экземпляром, когда эти случаи больше не требуются. Это особенно важно в Windows95 и Windows98, где операционная система имеет ограниченную количество доступных контекстов устройства.

Изображение javadoc snippet:

Код приложения должен явно ссылаться на метод Image.dispose(), чтобы освободить ресурсы операционной системы, управляемые каждым экземпляром, когда эти экземпляры больше не требуются.

9

Интервьюер, возможно, искал круглое справочное решение:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

Это классическая проблема со ссылкой на сборщики мусора. Затем вы вежливо объясните, что JVM используют гораздо более сложный алгоритм, который не имеет этого ограничения.

-Wes Tarle

  • 10
    Это классическая проблема с подсчетом ссылок сборщиками мусора. Даже 15 лет назад Java не использовала подсчет ссылок. Ссылка Подсчет также медленнее, чем GC.
  • 2
    Не утечка памяти. Просто бесконечный цикл.
Показать ещё 2 комментария
8

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

В настоящее время верхний ответ содержит больше трюков, но они кажутся избыточными.

8

Теоретически вы не можете. Модель памяти Java предотвращает это. Однако, поскольку Java должен быть реализован, есть некоторые предостережения, которые вы можете использовать. В зависимости от того, что вы можете использовать:

  • Если вы можете использовать native, вы можете выделить память, которую вы позже не откажете.

  • Если это не доступно, есть небольшая тайна о java, которую не так много знают люди. Вы можете запросить массив прямого доступа, который не управляется GC, и поэтому его можно легко использовать для утечки памяти. Это обеспечивается DirectByteBuffer (http://download.oracle.com/javase/1.5.0/docs/api/java/nio/ByteBuffer.html#allocateDirect(int)).

  • Если вы не можете использовать какие-либо из них, вы все равно можете сделать утечку памяти, обманув GC. JVM реализуется с использованием сборщика мусора Generational. Это означает, что куча разделена на области: молодые, взрослые и старейшины. Объект, когда его созданный начинается в молодой области. Поскольку он используется все больше и больше, он переходит к взрослым до старейшин. Объект, который, скорее всего, попадает в область пожилых людей, скорее всего, не будет собран в мусор. Вы не можете быть уверены, что объект просочился, и если вы попросите остановить и очистить GC, он может его очистить, но в течение длительного времени он будет просочиться. Дополнительная информация (http://java.sun.com/docs/hotspot/gc1.4.2/faq.html)

  • Кроме того, объекты класса не обязательно должны быть GC'ed. Могу ли я сделать это.

  • 2
    Собственная память, выделенная DirectByteBuffer, освобождается в финализаторе, когда DirectByteBuffer собирается сборщиком мусора. Это, конечно, не протекает.
7

Я хочу дать совет о том, как контролировать приложение на предмет утечек памяти с помощью инструментов, доступных в JVM. Он не показывает, как генерировать утечку памяти, но объясняет, как ее обнаружить с минимальными доступными инструментами.

Вы должны контролировать потребление памяти Java в первую очередь.

Самый простой способ сделать это - использовать утилиту jstat, которая поставляется с JVM.

jstat -gcutil <process_id> <timeout>

Он будет сообщать о потреблении памяти для каждого поколения (Young, Eldery и Old) и времени сбора мусора (Young и Full).

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

Затем вам нужно создать дамп памяти с помощью утилиты jmap:

jmap -dump:live,format=b,file=heap.bin <process_id>

Затем вам нужно проанализировать файл heap.bin с помощью Memory Analyzer, например Eclipse Memory Analyzer (MAT).

MAT проанализирует память и предоставит вам подозрительную информацию об утечках памяти.

7

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

"как создать утечку памяти с помощью Java?" является открытым вопросом, целью которого является оценка степени опыта разработчика.

Если я спрошу вас: "У вас есть опыт устранения неполадок утечки памяти в Java?", ваш ответ будет простым "Да". Затем я должен был бы проследить за "Не могли бы вы привести примеры, где вы хотите устранить утечки памяти?", На которые вы могли бы привести мне один или два примера.

Однако, когда интервьюер спрашивает "как создать утечку памяти с помощью Java?" ожидаемый ответ должен следовать следующим строкам:

  • Я столкнулся с утечкой памяти... (скажем, когда) [показывает мне опыт]
  • Код, вызывающий это, был... (объясните код) [вы исправили его самостоятельно]
  • Исправление, к которому я применил, основывалось на... (объясните исправление) [это дает мне возможность задать специфику исправления]
  • Тест, который я сделал, был... [дает мне возможность просить другие методологии тестирования]
  • Я зарегистрировал его таким образом... [дополнительные очки. Хорошо, если вы задокументировали это.
  • Итак, разумно подумать, что если мы будем следовать этому в обратном порядке, то есть получить код, который я исправил, и удалить мое исправление, что у нас будет утечка памяти.

Когда разработчик не выполняет эту мысль, я пытаюсь спросить его: "Не могли бы вы привести мне пример того, как может произойти утечка памяти Java?", а затем "У вас когда-либо было исправление утечки памяти в Java?"

Обратите внимание, что я не запрашиваю пример о том, как утечка памяти в Java. Это было бы глупо. Кому будет интересен разработчик, который может эффективно писать код, который утечки памяти?

  • 0
    Что касается последнего предложения, лучший способ победить зло - это хорошо знать его. Если вы хотите написать защищенное веб-приложение, вам следует ознакомиться с наиболее распространенными методами и уязвимостями, такими как инъекции SQL или переполнение буфера. Точно так же, если вы хотите писать код без утечек, вы должны по крайней мере описать наиболее распространенные способы утечки памяти, такие как потерянные указатели в C / C ++. Определенно менее простой в Java, хотя.
7

Выбросить необработанное исключение из метода finalize.

7

Если максимальный размер кучи X. Y1.... Yn нет экземпляров Итак, общая память = количество экземпляров X байтов на экземпляр. Если X1...... Xn - это байты на экземпляры. Затем общая память ( M) = Y1 * X1 +..... + Yn * Xn. Итак, если M > X, оно превышает пустое пространство. следующие могут быть проблемы в коде 1. Используйте больше экземпляров переменной, чем локальную. 2.Создание экземпляров каждый раз вместо объединения объекта. 3. Не создавать объект по требованию. 4. Создание ссылки на объект null после завершения операции. Возможен, воссоздание, когда оно требуется в программе.

7

Несколько предложений:

  • использовать commons-logging в контейнере сервлетов (возможно, немного провокационный)
  • запустите поток в контейнере сервлета и не вернетесь из него.
  • загружать анимированные gifs в контейнер сервлетов (это запустит поток анимации)

Вышеуказанные эффекты могут быть "улучшены" путем перераспределения приложения;)

Недавно наткнулся на это:

  • Вызов "new java.util.zip.Inflater();" без вызова "Inflater.end()" ever

Прочитайте http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5072161 и увязывайте вопросы для углубленного обсуждения.

  • 0
    Чтобы добавить к дискуссии о том, что утечка памяти, на мой взгляд, важна идея «преднамеренного». Конечно, поле (статическое или нет), указывающее на огромную структуру данных, еще не утечка памяти. Но если указанная структура данных бесполезна для вашей логики, она просто задерживается, загрязняя вашу кучу, возможно, когда-либо увеличиваясь, пока GC не сдается с OOME, тогда я называю это «утечкой памяти». Утечка памяти просто больше не доступна для меня. Точно так же, как в прежние времена, когда я звонил malloc, никогда не возвращая это, чтобы очистить для спасения.
  • 0
    Инфлятор хорош, хотя и не является настоящей утечкой (если будет завершена), он очень хорошо пожирает родную память и его трудно диагностировать. Вы заслуживаете голоса (но я не голосую сам). Я добавил еще немного информации в свой ответ (раньше не видел ваш)
Показать ещё 2 комментария
7

Большинство утечек памяти, которые я видел в java-процессах, выходят из синхронизации.

Процесс A разговаривает с B через TCP и сообщает процессу B что-то создать. B выдает ресурс ID, например 432423, который A хранит в объекте и использует во время разговора с B. В какой-то момент объект в восстанавливается сбором мусора (возможно, из-за ошибки), но A никогда не сообщает B, что ( возможно, другая ошибка).

Теперь A не имеет идентификатора объекта, который он создал в B RAM, и B не знает, что A больше не ссылается на объект. По сути, объект просочился.

6

Утечка памяти в Java не является типичной утечкой памяти в C/C++.

Чтобы понять, как работает JVM, прочитайте раздел "Управление памятью".

В основном, важная часть:

Модель Марка и Сметания

JRockit JVM использует модель сборки мусора меток и зачисток для выполнения сборок мусора всей кучи. Сбор мусора с меткой и уборкой состоит из двух фаз: фазы маркировки и фазы уборки.

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

Во время фазы развертки проходит куча, чтобы найти промежутки между живыми объектами. Эти пробелы записываются в свободном списке и становятся доступными для размещения новых объектов.

В JRockit JVM используются две улучшенные версии модели mark и sweep. Один в основном совпадает с меткой и разверткой, а другой - с параллельной меткой и разверткой. Вы также можете смешать две стратегии, например, в основном для одновременной метки и параллельной развертки.

Итак, чтобы создать утечку памяти в Java; самый простой способ сделать это - создать соединение с базой данных, выполнить некоторую работу и просто не Close(); затем создайте новое соединение с базой данных, оставаясь в области видимости. Это не сложно сделать в цикле, например. Если у вас есть работник, который извлекает данные из очереди и передает их в базу данных, вы можете легко создать утечку памяти, забыв о соединениях Close() или открыв их, когда это не нужно, и так далее.

В конце концов, вы будете использовать кучу, выделенную для JVM, забыв Close() соединение. Это приведет к сбору мусора в JVM как сумасшедшему; в конечном итоге приводит к java.lang.OutOfMemoryError: Java heap space ошибки java.lang.OutOfMemoryError: Java heap space. Следует отметить, что ошибка может не означать утечку памяти; это может означать, что у вас недостаточно памяти; базы данных, такие как Cassandra и ElasticSearch, например, могут выдавать эту ошибку, потому что у них недостаточно места в куче.

Стоит отметить, что это верно для всех языков GC. Ниже приведены некоторые примеры работы в качестве SRE:

  • Узел, использующий Redis в качестве очереди; Команда разработчиков создавала новые соединения каждые 12 часов и забыла закрыть старые. В конце концов узел был OOMd, потому что он занимал всю память.
  • Голанг (я виноват в этом); парсинг больших файлов json с json.Unmarshal а затем передача результатов по ссылке и сохранение их открытыми. В конечном итоге это привело к тому, что вся куча была поглощена случайными ссылками, которые я оставил открытыми для декодирования json.
6

Одна из возможностей - создать оболочку для ArrayList, которая предоставляет только один метод: тот, который добавляет вещи в ArrayList. Сделайте сам ArrayList приватным. Теперь создайте один из этих объектов-оболочек в глобальной области (как статический объект в классе) и квалифицируйте его ключевым словом final (например, public static final ArrayListWrapper wrapperClass = new ArrayListWrapper()). Так что теперь ссылка не может быть изменена. То есть wrapperClass = null не будет работать и не может использоваться для освобождения памяти. Но также нет никакого способа сделать что-либо с wrapperClass, кроме добавления к нему объектов. Поэтому любые объекты, которые вы добавляете в wrapperClass, невозможно переработать.

5

Метод String.substring в java 1.6 создает утечку памяти. Это сообщение в блоге объясняет это.

http://javarevisited.blogspot.com/2011/10/how-substring-in-java-works.html

5

У Swing очень простое диалоговое окно. Создайте JDialog, покажите его, пользователь закрывает его, течет! Вы должны вызвать dispose() или настроить setDefaultCloseOperation(DISPOSE_ON_CLOSE)

5

В Java "утечка памяти" - это прежде всего использование слишком большого объема памяти, отличное от того, на котором вы больше не используете память, но забываете вернуть ее (бесплатно). Когда собеседник спрашивает о утечке памяти Java, они спрашивают о том, что использование памяти JVM просто продолжает расти, и они решили, что перезапуск JVM на регулярной основе является лучшим решением. (если интервьюер не является чрезвычайно технически подкованным)

Итак, ответьте на этот вопрос, как будто они спрашивают, что увеличивает использование памяти JVM с течением времени. Хорошие ответы будут хранить слишком много данных в HttpSessions с чрезмерно длинным таймаутом или плохо реализованным кэшем в памяти (Singleton), который никогда не сбрасывает старые записи. Другим потенциальным ответом является наличие множества JSP или динамически генерируемых классов. Классы загружаются в область памяти PermGen, которая обычно мала, и большинство JVM не реализуют разгрузку классов.

4

Небрежно Использование нестатического внутреннего класса внутри класса, у которого есть свой жизненный цикл.

В Java нестатические внутренние и анонимные классы содержат неявные ссылки для своего внешнего класса. Статические внутренние классы, с другой стороны, не.

Вот типичный пример утечки памяти в Android, что не очевидно:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() { //non-static inner class, holds the reference to the SampleActivity outter class
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for a long time.
    mLeakyHandler.postDelayed(new Runnable() {//here, the anonymous inner class holds the reference to the SampleActivity class too
      @Override
      public void run() {
     //.... 
      }
    }, SOME_TOME_TIME);

    // Go back to the previous Activity.
    finish();
  }}

Это предотвратит сбор контекста активности.

  • 0
    mLeakyHandler статическое использование mLeakyHandler предотвратить утечку памяти? И есть ли (другие) способы предотвратить утечку активности в mLeakyHandler ? Кроме того, как бы вы решили эту проблему для анонимного внутреннего класса Runnable ?
  • 1
    @ ban-geoengineering Да, сделайте это статичным, и если вам нужно задействовать внешнюю активность, сделайте обработчик для хранения WeakReference для этой активности, пожалуйста, проверьте androiddesignpatterns.com/2013/01/…
4

Lapsed Listerners - хороший пример утечек памяти: объект добавляется как слушатель. Все ссылки на объект обнуляются, когда объект больше не нужен. Однако, забыв удалить объект из списка Listener, он сохраняет объект и даже реагирует на события, тем самым теряя память и процессор. См. http://www.drdobbs.com/jvm/java-qa/184404011

4

Если вы не используете компактный сборщик мусора, у вас может быть какая-то утечка памяти из-за фрагментации кучи.

0

утечка памяти - это тип утечки ресурсов, который возникает, когда компьютерная программа неправильно управляет выделением памяти таким образом, что память, которая больше не нужна, не освобождается => определение вики

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

Первый пример должен быть пользовательским стеком без обнуления устаревших ссылок в эффективном элементе 6 Java.

Конечно, есть еще много, сколько вы хотите, но если мы просто посмотрим на встроенные классы Java, это может быть

subList()

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

public class MemoryLeak {
    private static final int HUGE_SIZE = 10_000;

    public static void main(String... args) {
        letsLeakNow();
    }

    private static void letsLeakNow() {
        Map<Integer, Object> leakMap = new HashMap<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            leakMap.put(i * 2, getListWithRandomNumber());
        }
    }



    private static List<Integer> getListWithRandomNumber() {
        List<Integer> originalHugeIntList = new ArrayList<>();
        for (int i = 0; i < HUGE_SIZE; ++i) {
            originalHugeIntList.add(new Random().nextInt());
        }
        return originalHugeIntList.subList(0, 1);
    }
}

На самом деле есть еще один трюк, который мы можем вызвать утечку памяти, используя HashMap, воспользовавшись преимуществами его процесса поиска. На самом деле есть два типа:

  • hashCode() всегда одинаков, но equals() различаются;
  • используйте случайные hashCode() и equals() всегда true;

Зачем?

hashCode() → bucket => equals() чтобы найти пару


Я собирался сначала упомянуть substring() а затем subList() но, похоже, эта проблема уже исправлена, поскольку ее источник представлен.

public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);
}
0

Пример утечки памяти в реальном времени до JDK 1.7

Предположим, вы читаете файл из 1000 строк текста и сохраняете объект String

String fileText = 1000 characters from file

fileText = fileText.subString(900, fileText.length());

В приведенном выше коде я сначала прочитал 1000 символов, а затем сделал подстроку, чтобы получить только 100 последних символов. Теперь fileText должен ссылаться только на 100 символов, и все другие символы должны собирать мусор, так как я потерял ссылку, но до того, как функция подстроки JDK 1.7 косвенно ссылается на исходную строку из последних 100 символов, она предотвращает сбор всей строки и сборку из 1000 символов. в памяти, пока вы не потеряете ссылку на подстроку.

Вы можете создать пример утечки памяти, как указано выше

  • 0
    Я так не думаю. Новая строка создается и возвращается. Вот фрагмент кода из open-jdk 6 для возврата функции подстроки ((beginIndex == 0) && (endIndex == count))? this: новая строка (offset + beginIndex, endIndex - beginIndex, значение);
  • 1
    создается корректный объект новой строки, но если вы видите, что это передаваемое значение, которое является массивом char исходной строки, а вновь созданная строка сохраняет ссылку на полный массив char. Вы можете просто сравнить реализацию с Java 6 до 8, Java 7 и 8 использует Arrays.copyOfRange (значение, смещение, смещение + количество), чтобы вернуть фактическую подстроку
Показать ещё 1 комментарий
-3

Просто так!

public static void main(String[] args) {
    List<Object> objects = new ArrayList<>();
    while(true) {
        objects.add(new Object());
    }
}
  • 1
    Это не пример утечки памяти. Вы просто пытаетесь использовать всю память здесь со своим списком. Утечка памяти - это когда устаревшие ссылки не могут быть собраны сборщиком мусора.
-4

Из эффективной java-книги

  • , когда класс управляет собственной памятью, программист должен предупреждение о утечке памяти

.

public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {
    elements = new Object[DEFAULT_INITIAL_CAPACITY];
}

public void push(Object e) {
    ensureCapacity();
    elements[size++] = e;
}

public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    return elements[--size];
}

/**
 * Ensure space for at least one more element, roughly doubling the capacity
 * each time the array needs to grow.
 */
private void ensureCapacity() {
    if (elements.length == size)
        elements = Arrays.copyOf(elements, 2 * size + 1);
}

}

Вы можете обнаружить утечку памяти? Так где же утечка памяти? Если стек растет, а затем сжимается, объекты которые выскочили со стека, не будут собирать мусор, даже если программа использование стека не имеет больше ссылок на них. Это связано с тем, что стек поддерживает устаревшие ссылки на эти объекты. Устаревшая ссылка - это просто ссылка это никогда не будет разыменовано снова. В этом случае любые ссылки за пределами "активная часть" массива элементов устарела. Активная часть состоит из элементов, индекс которых меньше размера.

  • 0
    Почему это было отвергнуто? Это на самом деле взято из книги.
  • 0
    Я думаю, что это отрицательно, потому что этот пример не будет создавать устаревшие ссылки. Пока этот стек активен, каждая ссылка доступна через массив элементов. Даже если size изменяется каждый раз, когда вызывается pop , все еще возможно выполнить итерацию по всему массиву, используя его свойство length . Поэтому нет устаревших ссылок. Я должен сказать, что обычно коллекция освобождает владельца в функции, такой как pop хотя, но этот пример не создаст утечку памяти, которая является первоначальным вопросом
-4

В Java нет такой вещи, как утечка памяти. Утечка памяти - это фраза, заимствованная у C et al. Java имеет дело с распределением памяти внутри с помощью GC. Там расточительность памяти (т.е. Оставление многожильных объектов), но не утечка памяти.

-5

Вот очень простая Java-программа, которая выйдет из пространства

public class OutOfMemory {

    public static void main(String[] arg) {

        List<Long> mem = new LinkedList<Long>();
        while (true) {
            mem.add(new Long(Long.MAX_VALUE));
        }
    }
}
  • 33
    -1 это наверняка исчерпает память, потому что требование состоит в том, чтобы иметь бесконечное количество памяти. Я не называю это утечкой памяти. Это просто глупая программа.
  • 0
    Также это было уже предложено
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню