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




что такое утечка памяти java (20)

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

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

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

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

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

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

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

https://code.i-harness.com

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

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


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


Вы можете сделать утечку памяти с классом 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();
        }
    }

}

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


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

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

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

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

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

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


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

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

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

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

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

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


Ссылка на объект статического поля [конечное поле 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 .


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

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

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

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


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

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

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


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


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


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

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

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

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


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

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

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

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


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


Создайте статическую карту и продолжайте добавлять к ней жесткие ссылки. Те никогда не будут 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); }
}

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


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

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

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





memory-leaks