Создание утечки памяти с Java - PullRequest
2986 голосов
/ 24 июня 2011

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

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

Ответы [ 54 ]

44 голосов
/ 02 июля 2011

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

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

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

Все остальные ответы до сих пор находятся в моемопределение не действительно утечки памяти.Все они стремятся заполнить память бессмысленными вещами очень быстро.Но в любой момент вы все равно можете разыменовать созданные вами объекты и, таким образом, освободить память -> НЕТ УТЕЧКИ. ответ acconrad подходит довольно близко, хотя, как я должен признать, его решение состоит в том, чтобы просто "разбить" сборщик мусора, запустив его в бесконечный цикл).

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

37 голосов
/ 21 июля 2011

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

36 голосов
/ 28 июня 2011

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

36 голосов
/ 04 июля 2011

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

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

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

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

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

34 голосов
/ 24 июня 2011

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

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

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

30 голосов
/ 30 июня 2011

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

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

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

23 голосов
/ 18 июля 2014

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

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

Типичнопример:

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

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

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

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

23 голосов
/ 21 июля 2011

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

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

Чтобы хранить специфичные для потока теги, в классе NDC log4j используется Hashtable, который управляется самим объектом Thread (в отличие от, скажем, идентификатора потока), и, таким образом, до тех пор, пока тег NDC не останется в памяти, все объекты зависают объекта потока также остаются в памяти. В нашем веб-приложении мы используем NDC для пометки выходов из системы с помощью идентификатора запроса, чтобы отдельно отличать журналы от одного запроса. Контейнер, который связывает тег 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, является встроенным потоком, в котором висит объект largeList. Следовательно, даже после того, как поток завершил делать то, что делал, ссылка на огромный список поддерживалась контекстной таблицей NDC, что приводило к утечке памяти.

22 голосов
/ 09 июля 2011

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

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

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

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

19 голосов
/ 13 октября 2017

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

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