Вопрос: Создание утечки памяти с помощью Java


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

Какой пример?


2593


источник


Ответы:


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

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

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

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

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

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


1909



Ссылка на объект статического поля [конечное поле esp]

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();

(Unclosed) открытые потоки (файл, сеть и т. Д.)

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 ,


1050



Простая вещь - использовать 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.

387



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

  • File.deleteOnExit()- всегда течет строка, если строка является подстрокой, утечка еще хуже (основной символ [] также просочился) - в подстроке 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 бит (максимум) и 8 уровней памяти (максимум 9), выделяя несколько сотен КБ встроенной памяти. К счастью, Deflaterне широко используется, и, насколько мне известно, JDK не содержит злоупотреблений. Всегда звоните end()если вы вручную создаете Deflaterили Inflater, Лучшая часть последних двух: вы не можете найти их через обычные инструменты для профилирования.

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

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


225



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

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

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

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

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

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

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


145



The answer depends entirely on what the interviewer thought they were asking.

Is it possible in practice to make Java leak? Of course it is, and there are plenty of examples in the other answers.

But there are multiple meta-questions that may have been being asked?

  • Is a theoretically "perfect" Java implementation vulnerable to leaks?
  • Does the candidate understand the difference between theory and reality?
  • Does the candidate understand how garbage collection works?
  • Or how garbage collection is supposed to work in an ideal case?
  • Do they know they can call other languages through native interfaces?
  • Do they know to leak memory in those other languages?
  • Does the candidate even know what memory management is, and what is going on behind the scene in Java?

I'm reading your meta-question as "What's an answer I could have used in this interview situation". And hence, I'm going to focus on interview skills instead of Java. I believe your more likely to repeat the situation of not knowing the answer to a question in an interview than you are to be in a place of needing to know how to make Java leak. So, hopefully, this will help.

One of the most important skills you can develop for interviewing is learning to actively listen to the questions and working with the interviewer to extract their intent. Not only does this let you answer their question the way they want, but also shows that you have some vital communication skills. And when it comes down to a choice between many equally talented developers, I'll hire the one who listens, thinks, and understands before they respond every time.


131



The following is a pretty pointless example, if you do not understand JDBC. Or at least how JDBC expects a developer to close Connection, Statement and ResultSet instances before discarding them or losing references to them, instead of relying on the implementation of 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);
   }
}

The problem with the above is that the Connection object is not closed, and hence the physical connection will remain open, until the garbage collector comes around and sees that it is unreachable. GC will invoke the finalize method, but there are JDBC drivers that do not implement the finalize, at least not in the same way that Connection.close is implemented. The resulting behavior is that while memory will be reclaimed due to unreachable objects being collected, resources (including memory) associated with the Connection object might simply not be reclaimed.

In such an event where the Connection's finalize method does not clean up everything, one might actually find that the physical connection to the database server will last several garbage collection cycles, until the database server eventually figures out that the connection is not alive (if it does), and should be closed.

Even if the JDBC driver were to implement finalize, it is possible for exceptions to be thrown during finalization. The resulting behavior is that any memory associated with the now "dormant" object will not be reclaimed, as finalize is guaranteed to be invoked only once.

The above scenario of encountering exceptions during object finalization is related to another other scenario that could possibly lead to a memory leak - object resurrection. Object resurrection is often done intentionally by creating a strong reference to the object from being finalized, from another object. When object resurrection is misused it will lead to a memory leak in combination with other sources of memory leaks.

There are plenty more examples that you can conjure up - like

  • Managing a List instance where you are only adding to the list and not deleting from it (although you should be getting rid of elements you no longer need), or
  • Opening Sockets or Files, but not closing them when they are no longer needed (similar to the above example involving the Connection class).
  • Not unloading Singletons when bringing down a Java EE application. Apparently, the Classloader that loaded the singleton class will retain a reference to the class, and hence the singleton instance will never be collected. When a new instance of the application is deployed, a new class loader is usually created, and the former class loader will continue to exist due to the singleton.

112



Probably one of the simplest examples of a potential memory leak, and how to avoid it, is the implementation of 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;
}

If you were implementing it yourself, would you have thought to clear the array element that is no longer used (elementData[--size] = null)? That reference might keep a huge object alive ...


101



Any time you keep references around to objects that you no longer need you have a memory leak. See Handling memory leaks in Java programs for examples of how memory leaks manifest themselves in Java and what you can do about it.


62