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

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

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

63 ответа

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

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 {

}

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

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

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

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

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

Фрагмент GC Javadoc:

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

Фрагмент изображения Javadoc:

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

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

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

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

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

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

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

  • Кроме того, объекты класса не обязательно должны быть GC-объектами. Дай мне способ сделать это.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • использовать комм-регистрацию в контейнере сервлетов (возможно, немного провокационно)
  • запустить поток в контейнере сервлета и не возвращаться из его метода run
  • загрузить анимированные GIF-файлы в контейнер сервлета (это запустит анимационный поток)

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

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

  • Вызов "new java.util.zip.Inflater();" без вызова "Inflater.end()" никогда

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

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

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

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

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

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

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

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

Это предотвратит сбор мусора в контексте действия.

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

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

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

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

Первый пример должен быть пользовательским стеком без обнуления устаревших ссылок в эффективном элементе 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() всегда правда;

Зачем?

hashCode() -> ведро => equals() найти пару


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

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

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

class Main {
    public static void main(String args[]) {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            ((Unsafe) f.get(null)).allocateMemory(2000000000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Это довольно просто:

          Object[] o = new Object[]{};
    while(true) {
        o = new Object[]{o};
    }

Я испытал очень реальную утечку памяти сjavax.swing.JPopupMenu.

У меня есть приложение с графическим интерфейсом, которое отображает несколько документов с вкладками. После закрытия документа он оставался в памяти, если для любого компонента на вкладке использовалось контекстное меню правой кнопки мыши. Меню были разделены между вкладками, и получается, что после вызоваpopupMenu.show(Component invoker, int x, int y), компонент спокойно сохраняется как «вызывающий» меню до тех пор, пока он не будет изменен или очищенsetInvoker(null). Косвенно ссылка вызывающего объекта сохраняла весь документ и все, что с ним связано.

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

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

while(true) {
    ResultSet rs = database.select(query);
    ...
    // going to next step of loop and leaving resultset without calling rs.close();
}

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

Пример утечки памяти в реальном времени до 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 символов. в памяти, пока вы не потеряете ссылку на подстроку.

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

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

Например

static List<int> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
    list.add(i);
}

system.sleep();

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

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

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

Именно так!

public static void main(String[] args) {
    List<Object> objects = new ArrayList<>();
    while(true) {
        objects.add(new Object());
    }
}
Другие вопросы по тегам