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

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

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

30 ответов


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

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

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

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

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

обновление: так много людей просят, вот пример кода, который демонстрирует это поведение в действии.


статическое поле, содержащее ссылку на объект [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();

(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, например, память, выделенная через native методы

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

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

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

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


простая вещь - использовать хэш-набор с неправильным (или несуществующим) 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.

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

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

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

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

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

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

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

  • вызов ThreadGroup.destroy() когда у ThreadGroup нет потоков, но он все еще сохраняет дочерние ThreadGroups. Плохая утечка, которая помешает ThreadGroup удалить из своего родителя, но все дочерние элементы становятся не перечисляемыми.

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

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

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

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

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

удачи и оставаться в безопасности; утечки-это зло!


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

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

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

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

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

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

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


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

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

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

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

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

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


следующий довольно бессмысленный пример, если вы не понимаете 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, по крайней мере, не так, как есть. Результирующее поведение заключается в том, что, хотя память будет восстановлена из-за собираемых недостижимых объектов, ресурсы (включая память), связанные с


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

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)? Эта ссылка может сохранить огромный объект живым ...


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


Вы можете сделать утечку памяти с солнце.разное.Небезопасно!--4--> класса. Фактически этот класс обслуживания используется в разных стандартных классах (например, в java.НИО!--4--> классы). вы не можете создать экземпляр этого класса напрямую, а может использовать отражение, чтобы сделать это.

код не компилируется в 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();
        }
    }

}

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

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

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

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

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


вот простой / зловещий через 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 будет отброшен.


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


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

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

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

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

потоки, запущенные вашим применение, ThreadLocal переменные, Регистрация appenders некоторые из обычных подозреваемых, чтобы вызвать утечки classloader.


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

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

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


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

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

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


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

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

для хранения тегов, специфичных для потока, класс NDC log4j использует хэш-таблицу, которая Сам объект потока (в отличие от идентификатора потока), и, таким образом, пока тег 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 был связан со встроенным потоком, который был порожден. Объект thread, который был ключом для этого контекста NDC, является встроенным потоком, на котором висит объект hugeList. Поэтому даже после того, как поток закончил делать то, что он делал, ссылка на hugeList была сохранена в контексте NDC Hastable, что вызвало утечку памяти.


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

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

теперь, если вы вызываете Example1 и получаете Example2, отбрасывающий 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 предполагает, что она всегда будет существовать и на самом деле никогда не будет пытаться очистить ее, если больше не может быть достигнуто в коде. Но это совершенно непроверено.


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

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

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

кэш объектов является хорошей отправной точкой для беспорядка.

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's 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 конструкций.
  • добавлять больше комплекс контрольных деревьев.
  • или утечки, вызванные 3-й партии библиотеки.

что часто бывает так:

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


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

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

вы можете создать утечку движущейся памяти, создав новый экземпляр класса в методе 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());
        }
    }
}

интервьюер, вероятно, искал круговую ссылку, такую как код ниже (который, кстати, только утечка памяти в очень старых JVMs, которые использовали подсчет ссылок, чего больше нет). Но это довольно расплывчатый вопрос, поэтому это отличная возможность показать свое понимание управления памятью 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 */
}

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

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

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.


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

  1. объявить собственный метод.
  2. в собственном методе, называют malloc. Не звони free.
  3. вызовите собственный метод.

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


недавно я столкнулся с более тонкой утечкой ресурсов. Мы открываем ресурсы через 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);
    }

}

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

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

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

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

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

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

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

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


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


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

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

в одну сторону, что используется B-могут быть безопасно собраны, если A и B не доступны ничем другим, но не могут обрабатывать трехстороннюю цепь...


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

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 {

}

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

рассмотрим: основной шаблон для завершения рабочего потока-установить некоторую переменную условия, видимую потоком. Поток может периодически проверять переменную и использовать ее в качестве сигнала для завершения. Если переменная не объявлена 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 никогда не умрет.

(*отредактировано*)


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

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

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


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

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

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