Как я могу создать утечку памяти в Java?

avatar
Peter Mortensen
24 июня 2011 в 16:11
692238
59
3493

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

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

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

Источник
Peter - Reinstate Monica
12 мая 2021 в 15:00
11

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

Galik
23 июля 2021 в 04:58
0

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

Ответы (59)

avatar
Daniel Pryden
26 ноября 2019 в 15:31
2465

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

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

Из-за того, как ThreadLocal реализован в Oracle JDK, это создает утечку памяти:

  • Каждый Thread имеет личное поле threadLocals, в котором фактически хранятся локальные значения потока.
  • Каждый ключ на этой карте является слабой ссылкой на объект ThreadLocal, поэтому после этого объекта ThreadLocal выполняется сборка мусора, его запись удаляется с карты.
  • Но каждое значение является сильной ссылкой, поэтому, когда значение (прямо или косвенно) указывает на объект ThreadLocal, который является его ключом , этот объект не будет мусором. -собирается и не удаляется с карты, пока живет нить.

В этом примере цепочка сильных ссылок выглядит так:

Thread объект → threadLocals карта → экземпляр класса примера → пример класса → статическое поле ThreadLocalThreadLocal объект.

(ClassLoader на самом деле не играет роли в создании утечки, он просто усугубляет утечку из-за этой дополнительной цепочки ссылок: example class → ClassLoader → все классы, которые он загрузил. Это было даже хуже во многих реализациях JVM, особенно до Java 7, потому что классы и ClassLoader выделялись прямо в permgen и никогда не собирались сборщиком мусора.)

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

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

earcam
27 июня 2011 в 16:55
209

+1 Утечки ClassLoader - одни из наиболее распространенных утечек памяти в мире JEE, часто вызванные сторонними библиотеками, которые преобразуют данные (BeanUtils, кодеки XML / JSON). Это может произойти, когда библиотека загружается за пределами корневого загрузчика классов вашего приложения, но содержит ссылки на ваши классы (например, путем кеширования). Когда вы отменяете развертывание / повторно развертываете свое приложение, JVM не может собрать мусор загрузчик классов приложения (и, следовательно, все классы, загруженные им), поэтому при повторном развертывании сервер приложений в конечном итоге перестает работать. Если повезет, вы получите подсказку с ClassCastException z.x.y.Abc не может быть преобразован в z.x.y.Abc

Adrian M
8 июля 2011 в 09:08
63

+1: Утечки Classloader - это кошмар. Я потратил недели, пытаясь понять их. Печально то, что, как сказал @earcam, они в основном вызваны сторонними библиотеками, а также большинство профилировщиков не могут обнаружить эти утечки. В этом блоге есть хорошее и четкое объяснение утечек Classloader. blogs.oracle.com/fkieviet/entry/…

avatar
6 июля 2021 в 06:44
0

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

avatar
Tashkhisi
13 мая 2021 в 00:06
6

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

Пример 1:

Вот хороший пример утечки памяти из книги Эффективная Java, третье издание (пункт 7: Устранение устаревших ссылок на объекты):

// Can you spot the "memory leak"?
public class Stack {
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private Object[] elements;
    private int size = 0;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        return elements[--size];
    }

    /*** Ensure space for at least one more element, roughly* doubling the capacity each time the array needs to grow.*/
    private void ensureCapacity() {
        if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

Этот абзац книги описывает, почему эта реализация вызовет утечку памяти:

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

Вот решение книги для устранения этой утечки памяти:

Решение этой проблемы простое: обнулить ссылки, когда они устареют. В случае нашего класса Stack, ссылка на элемент становится устаревшим, как только он появляется со стека. Исправленная версия метода pop выглядит так:

public Object pop() {
    if (size == 0) throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // Eliminate obsolete reference
    return result;
}

Но как предотвратить утечку памяти? Это хорошая оговорка из книги:

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

Пример 2:

Шаблон наблюдателя также может вызвать утечку памяти. Вы можете прочитать об этом шаблоне по следующей ссылке: Шаблон наблюдателя.

Это одна из реализаций шаблона наблюдателя:

class EventSource {
    public interface Observer {
        void update(String event);
    }

    private final List<Observer> observers = new ArrayList<>();

    private void notifyObservers(String event) {
        observers.forEach(observer -> observer.update(event)); //alternative lambda expression: observers.forEach(Observer::update);
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void scanSystemIn() {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            notifyObservers(line);
        }
    }
}

В этой реализации EventSource, который является наблюдаемым в шаблоне проектирования Observer, может содержать ссылку на объекты Obeserver, но эта ссылка никогда не удаляется из поля observers в EventSource. Таким образом, они никогда не будут собраны сборщиком мусора. Одним из решений этой проблемы является предоставление клиенту другого метода удаления вышеупомянутых наблюдателей из поля observers, когда они больше не нужны:

public void removeObserver(Observer observer) {
    observers.remove(observer);
}
avatar
Amir Fo
13 мая 2021 в 00:01
0

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

while(true) {
    ResultSet rs = database.select(query);
    ...
    // going to next step of loop and leaving resultset without calling rs.close();
}
avatar
Hearen
12 мая 2021 в 23:59
1

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

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

Первым примером должен быть пользовательский стек без обнуления устаревших ссылок в Эффективная Java, элемент 6.

Конечно, их гораздо больше, чем вы хотите, но если мы просто взглянем на встроенные классы 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 () -> bucket => 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);
}
avatar
Pavel Molchanov
12 мая 2021 в 22:10
8

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

Сначала необходимо отслеживать потребление памяти Java.

Самый простой способ сделать это - использовать утилиту jstat , которая поставляется с JVM:

jstat -gcutil <process_id> <timeout>

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

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

Затем нужно создать дамп памяти с помощью утилиты jmap :

jmap -dump:live,format=b,file=heap.bin <process_id>

Затем вам нужно проанализировать файл heap.bin с помощью анализатора памяти , Eclipse Memory Analyzer (MAT), например.

MAT проанализирует память и предоставит вам подозрительную информацию об утечках памяти.

avatar
Praveen Kumar
12 мая 2021 в 22:05
0

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

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

Karan Khanna
19 апреля 2018 в 09:12
0

Я так не думаю. Создается и возвращается новая строка. Вот фрагмент кода из open-jdk 6 для возврата функции subString ((beginIndex == 0) && (endIndex == count))? это: новая строка (смещение + beginIndex, endIndex - beginIndex, значение);

Praveen Kumar
19 апреля 2018 в 09:27
1

создается правильный новый строковый объект, но если вы видите, что он передает значение, которое представляет собой массив символов исходной строки, а вновь созданная строка сохраняет ссылку на полный массив символов. вы можете просто сравнить реализацию от java 6 до 8, java 7 и 8 использует Arrays.copyOfRange (значение, смещение, смещение + счетчик) для возврата фактической подстроки

Karan Khanna
19 апреля 2018 в 09:38
0

Понятно. Спасибо.

Peter Mortensen
12 мая 2021 в 22:06
0

Что вы имеете в виду под «в реальном времени»?

avatar
deltamind106
12 мая 2021 в 22:03
24

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

avatar
12 мая 2021 в 22:01
6

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

Чтобы понять, как работает JVM, прочтите Общие сведения об управлении памятью.

По сути, важная часть:

Модель Mark and Sweep

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

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

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

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

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

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

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

  • Node.js с использованием Redis в качестве очереди; команда разработчиков создавала новые подключения каждые 12 часов и забывала закрыть старые. В конце концов узел был OOMd, потому что он потреблял всю память.
  • Иди (в этом я виноват); анализ больших файлов JSON с помощью json.Unmarshal, а затем передача результатов по ссылке и сохранение их открытыми. В конце концов, это привело к тому, что вся куча была занята случайными ссылками, которые я оставил открытыми для декодирования JSON.
avatar
gurubelli
12 мая 2021 в 21:54
-4

Из книги Effective Java (написанной Джошуа Блохом):

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

    public class Stack {
    
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
        public Stack() {
            elements = new Object[DEFAULT_INITIAL_CAPACITY];
        }
    
        public void push(Object e) {
            ensureCapacity();
            elements[size++] = e;
        }
    
        public Object pop() {
            if (size == 0)
                throw new EmptyStackException();
            return elements[--size];
        }
    
        /**
         * Ensure space for at least one more element, roughly doubling the capacity
         * each time the array needs to grow.
         */
        private void ensureCapacity() {
            if (elements.length == size)
                elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
    

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

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

kiltek
2 августа 2018 в 07:19
0

Почему это было отклонено? Это фактически взято из книги.

0xDEADC0DE
27 декабря 2018 в 13:52
0

Я думаю, что он отклонен, потому что в этом примере не будут созданы устаревшие ссылки. Пока этот стек жив, каждая ссылка доступна через массив elements. Даже если size изменяется каждый раз, когда вызывается pop, по-прежнему можно перебирать весь массив, используя его свойство length. Поэтому нет устаревших ссылок. Я должен сказать, что обычно коллекция освобождает право собственности на такую ​​функцию, как pop, но этот пример не создает утечку памяти, что является исходным вопросом

avatar
JaskeyLam
12 мая 2021 в 21:52
4

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

В Java нестатические внутренние и анонимные классы содержат неявную ссылку на свой внешний класс. Статические внутренние классы, с другой стороны, не имеют .

Вот типичный пример утечки памяти в Android, который, однако, не очевиден:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() { // Non-static inner class, holds the reference to the SampleActivity outer 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();
  }}

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

ban-geoengineering
24 июня 2015 в 13:08
0

Может ли статический статический элемент mLeakyHandler предотвратить утечку памяти? И есть ли какие-либо (другие) способы предотвратить утечку информации о деятельности mLeakyHandler? Кроме того, как бы вы решили ту же проблему для анонимного внутреннего класса Runnable?

JaskeyLam
25 июня 2015 в 02:20
1

@ ban-geoengineering Да, сделайте его статическим, и если вам нужно задействовать внешнюю активность, сделайте так, чтобы обработчик содержал WeakReference для активности, пожалуйста, проверьте androiddesignpatterns.com/2013/01/…

avatar
Peter Mortensen
12 мая 2021 в 21:49
7

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

Как метод SubString работает в Java - утечка памяти устранена в JDK 1.7

avatar
Audrius Meskauskas
12 мая 2021 в 21:47
8

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

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

avatar
Boann
12 мая 2021 в 21:45
11

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

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

Boann
26 сентября 2013 в 23:39
1

@Spidey Ничего не "застревает". Вызывающий метод незамедлительно возвращается, и переданный объект никогда не будет возвращен. Это как раз утечка.

Spidey
27 сентября 2013 в 01:24
1

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

Boann
27 сентября 2013 в 01:39
1

@Spidey "У вас будет [вещь] на всю жизнь вашей программы. Для меня это не считается утечкой". Ты себя слышишь?

Spidey
27 сентября 2013 в 15:51
0

Да. Процесс по-прежнему ссылается на поток, и поток по-прежнему ссылается на объект o. Возможно, вы неправильно поняли, что такое утечка памяти.

Boann
27 сентября 2013 в 18:00
3

@Spidey Если вы посчитаете память, о которой процесс знает, как не утечку, тогда все ответы здесь неверны, поскольку процесс всегда отслеживает, какие страницы в его виртуальном адресном пространстве отображаются. Когда процесс завершается, ОС устраняет все утечки, помещая страницы обратно в стопку свободных страниц. Чтобы довести это до следующей крайности, можно забить до смерти любую спорную утечку, указав, что ни один из физических битов в микросхемах ОЗУ или в пространстве подкачки на диске не был физически потерян или уничтожен, поэтому вы можете выключить компьютер. и снова, чтобы устранить любую утечку.

Boann
27 сентября 2013 в 18:00
1

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

Boann
1 октября 2013 в 18:17
0

@Spidey Каков твой ответ? Что вы считаете утечкой?

Spidey
2 октября 2013 в 14:06
0

Поскольку в Java есть сборщик мусора, я не думаю, что он может даже протекать.

Boann
2 октября 2013 в 14:22
0

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

Spidey
2 октября 2013 в 18:07
0

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

Boann
2 октября 2013 в 19:36
0

@Spidey Это самая важная разница. На более низком уровне (программа -> JVM -> ОС -> микросхемы ОЗУ -> Вселенная) любая утечка памяти с более высокого уровня становится нереальной. Практическое определение утечки памяти состоит в том, что во время выполнения ваше пространство памяти (на каком бы уровне вы его ни определяли) накапливает вещи, которые не нужны, уменьшая доступное пространство для вещей, которые требуются. «В программе на C утечка нигде не упоминается» - тоже неверно - на утечку памяти все же можно ссылаться. Важно то, можно ли повторно использовать память, что в C не зависит от того, есть ли на нее ссылка.

Spidey
2 октября 2013 в 23:19
0

очевидно, что мало кто думает, что вы правы. См. Это: ibm.com/developerworks/rational/library/05/0816_GuptaPalanki Утечки памяти в Java - это не настоящие утечки, это плохой выбор дизайна или ошибки в коде.

Boann
3 октября 2013 в 16:14
0

@Spidey "мало кто думает, что вы правы" .. О чем? Какие люди? Где? Посмотрите на предыдущую страницу ответов на многие старые, популярные ответы, которые я защищаю здесь и которые вы называете неправильными. «Утечки памяти в Java не являются настоящими утечками» Нигде в статье этого не говорится. В нем приведены примеры утечек памяти и показано, как они представляют собой реальную потерю полезной памяти из-за того, что объекты не восстанавливаются. Эти объекты со временем накапливаются и становятся настоящей проблемой. Вот что такое утечка памяти. "это плохой выбор дизайна или ошибки в коде" ... которые приводят к утечкам памяти , да.

Spidey
3 октября 2013 в 17:23
0

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

Bill K
3 октября 2013 в 22:02
0

Это абсолютно правильный код, это то, что означает использование потоков. Они предназначены для хранения своих ссылок. Кроме того, это делают не только потоки, но и рамки графического интерфейса пользователя сохраняют все, на что они ссылаются, даже если нет потока. Есть еще несколько «вершин» дерева GC. Если у вас есть поток, который работает вечно, то по замыслу он навсегда сохранит все, на что ссылается, иначе ничего не будет работать. Однако, если это не то, что вы планировали, значит, у вас ДЕЙСТВИТЕЛЬНАЯ программная ошибка!

Boann
4 октября 2013 в 04:30
1

@Spidey "Я могу принять это как утечку памяти" Спасибо. Ранее вы сказали, что что-то не может быть утечкой, если на нее все еще есть ссылки, и что у вас не может быть утечки в Java, потому что в ней есть сборщик мусора. > _ <"Ваш ответ предполагает, что любой неиспользуемый выделенный объект подразумевает утечку". Что ж, пример представляет собой сценарий объекта, который сборщик мусора не может / не может собрать. Если это появляется в программе и повторяется неоднократно, это становится утечкой. Я постараюсь улучшить свой ответ с помощью этой информации.

Evan Plaice
24 февраля 2014 в 10:08
0

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

avatar
Peter Mortensen
12 мая 2021 в 21:42
7

Если максимальный размер кучи равен X. Y1 .... Yn количество экземпляров

Итак, общая память = количество экземпляров X байтов на экземпляр. Если X1 ...... Xn - это байты на экземпляры, тогда общая память (M) = Y1 * X1 + ..... + Yn * Xn. Итак, если M> X, он превышает размер кучи.

Следующие проблемы могут быть в коде

  1. Использование большего количества переменных экземпляров, чем локального.
  2. Создание экземпляров каждый раз вместо объединения объектов.
  3. Не создается объект по запросу.
  4. Обнуление ссылки на объект после завершения операции. Опять же, воссоздание, когда это требуется в программе.
avatar
Peter Mortensen
12 мая 2021 в 21:39
5

Swing очень легко справляется с диалогами. Создайте JDialog, покажите его, пользователь закроет и утечка!

Вы должны позвонить по телефону dispose() или настроить setDefaultCloseOperation(DISPOSE_ON_CLOSE).

avatar
Peter Mortensen
12 мая 2021 в 21:34
5

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

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

avatar
Peter
12 мая 2021 в 21:30
7

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

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

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

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

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

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

Peter
22 июля 2011 в 15:05
0

В дополнение к обсуждению того, что такое утечка памяти, на мой взгляд, важна идея «преднамеренной». Конечно, поле (статическое или нет), указывающее на огромную структуру данных, еще не является утечкой памяти. Но если указанная структура данных бесполезна для вашей логики, просто задерживается, загрязняет вашу кучу, возможно, когда-либо растет, пока GC не откажется от OOME, тогда я называю это «утечкой памяти». Утечка памяти просто мне больше не доступна. Как и в прежние времена, когда я вызывал malloc, даже не возвращая его в os - на помощь приходит purify.

bestsss
27 декабря 2011 в 01:17
0

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

Peter
2 февраля 2012 в 11:39
0

Finlizers - плохая идея, и нет никакой гарантии, что они когда-либо будут вызваны. Google для Cliff Click и его комментарии по этому поводу ...

bestsss
2 февраля 2012 в 15:04
0

Все JVM (за исключением JDK7 до 7.0.2) вызывают финализатор (ы) более или менее хорошо. Финализатор абсолютно необходим для работы прямых буферов (даже больше сопоставленных файлов, особенно в Windows). Они просто ненадежны (разумеется, я знаю, о чем говорит Клифф Клик)

avatar
arnt
12 мая 2021 в 21:23
7

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

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

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

avatar
Artur Ventura
12 мая 2021 в 21:19
8

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

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

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

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

  • Кроме того, GC не требует создания объектов класса. Возможно, есть способ сделать это.

Boann
31 августа 2013 в 05:04
2

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

avatar
Paul Morie
12 мая 2021 в 21:17
19

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

  1. Объявить собственный метод.
  2. В собственном методе вызовите malloc. Не звоните по номеру free.
  3. Вызвать собственный метод.

Помните, что выделение памяти в машинном коде происходит из кучи JVM.

avatar
Graham
12 мая 2021 в 21:16
14

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

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

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

assylias
20 июня 2013 в 23:01
7

Уже какое-то время этого не происходит. Современные сборщики мусора умеют обрабатывать циклические ссылки.

avatar
stemm
12 мая 2021 в 21:14
54

Вы можете сделать утечку памяти с помощью класса 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();
        }
    }
}
stemm
11 июля 2011 в 15:32
1

Выделенная память невидима для сборщика мусора

bestsss
17 июля 2011 в 18:35
4

Выделенная память также не принадлежит Java.

Berlin Brown
21 июля 2011 в 16:40
0

Является ли этот jvm солнцем / оракулом специфическим? Например. это будет работать на IBM?

stemm
22 июля 2011 в 13:50
0

@Berlin Brow На самом деле я не работал с jvm IBM. Но, как я уже сказал, некоторые стандартные пакеты (java.nio, java.util.concurrent и т. Д.) Используют этот класс обслуживания. Поэтому, если IBM jvm поддерживает использование этих библиотек, вероятно, у него есть собственный код для работы с sun.misc.Unsafe.

Michael Anderson
2 июня 2016 в 03:19
2

Память определенно «принадлежит Java», по крайней мере, в том смысле, что i) она недоступна никому другому, ii) при выходе из приложения Java она будет возвращена в систему. Это просто за пределами JVM.

Michael Anderson
2 июня 2016 в 03:21
3

Это будет встроено в eclipse (по крайней мере, в последних версиях), но вам нужно будет изменить настройки компилятора: в Window> Preferences> Java> Compiler> Errors / Warning> Deprecated and limited API set Forbidden reference (access rules) to «Предупреждение» ".

Darrell Teague
3 апреля 2018 в 18:58
0

Настоятельно советуем читателям учитывать, что поведение, специфичное для IDE, при разработке кода не только неуместно, но и опасно. А именно, стабильность и согласованность системы требуют единственного согласованного компилятора. IDE обычно использует "javac" некоторой версии от какого-либо поставщика / impl. Согласованность в поведении во время выполнения в рамках данной JVM зависит от одинаковой интерпретации байт-кода. Поскольку на серверах не работают IDE или их (вероятно, оснащенные инструментами) среды выполнения, опасно делать какие-либо предположения или аргументы относительно того, что данная IDE делает или не делает. Ссылайтесь только на экземпляр javac и JVM.

avatar
Ben Xu
12 мая 2021 в 21:13
10

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

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 exposes 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 {

}
avatar
Wesley Tarle
12 мая 2021 в 21:10
11

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

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

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

bestsss
5 июля 2011 в 19:36
13

Это классическая проблема со сборщиками мусора с подсчетом ссылок. Даже 15 лет назад в Java не использовался подсчет ссылок. Ref. подсчет также медленнее, чем GC.

Esben Skov Pedersen
21 июля 2011 в 15:15
5

Не утечка памяти. Просто бесконечный цикл.

rds
22 июля 2011 в 16:37
2

@Esben На каждой итерации предыдущий first бесполезен и должен собираться сборщиком мусора. В сборщиках мусора с подсчетом ссылок объект не будет освобожден, потому что на него есть активная ссылка (сама по себе). Бесконечный цикл предназначен для демонстрации утечки: когда вы запускаете программу, объем памяти увеличивается на неопределенный срок.

nz_21
6 ноября 2018 в 15:55
0

@rds @ Wesley Tarle предположим, что цикл был не бесконечным. Будет ли утечка памяти?

avatar
Harald Wellmann
12 мая 2021 в 21:09
37

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

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

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

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

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

Sven
22 мая 2018 в 05:23
1

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

avatar
yankee
12 мая 2021 в 21:06
44

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

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

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

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

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

Mason Wheeler
23 января 2014 в 19:54
14

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

Tomáš Zato - Reinstate Monica
5 января 2016 в 08:36
1

Упомянутый ответ acconrad был удален?

yankee
5 января 2016 в 11:14
1

@ TomášZato: Нет, это не так. Я перешел по ссылке выше, чтобы вы могли легко ее найти.

autistic
6 декабря 2016 в 17:08
0

Что такое воскрешение объекта? Сколько раз вызывается деструктор? Как эти вопросы опровергают этот ответ?

toolforger
3 ноября 2018 в 23:37
1

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

Andy
4 февраля 2021 в 07:42
0

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

Andy
4 февраля 2021 в 07:45
0

«в любое время вы все еще можете разыменовать созданные вами объекты» - вы могли бы, если бы вы отредактировали код, но во время выполнения, если ни один путь кода не будет разыменовывать эти объекты, их буквально невозможно освободить

avatar
Prashant Bhate
12 мая 2021 в 21:01
1281

Статическое поле, содержащее ссылку на объект [особенно final поле]

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

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

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.

Ian McLaird
13 июля 2011 в 04:08
200

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

bestsss
17 июля 2011 в 18:38
90

(Незакрытые) открытые потоки (файл, сеть и т. Д.) , на самом деле не протекает, во время финализации (которая будет после следующего цикла GC) close () будет запланировано (close() обычно не вызывается в потоке финализатора, так как это может быть операция блокировки). Не закрывать - плохая практика, но это не вызывает утечки. Незакрытый java.sql.Connection такой же.

mbauman
22 июля 2011 в 01:32
38

В большинстве нормальных JVM создается впечатление, что класс String имеет слабую ссылку на содержимое хэш-таблицы intern. Таким образом, это - это правильно собранный мусор, а не утечка. (но IANAJP) mindprod.com/jgloss/interned.html#GC

avatar
Sapphire_Brick
25 февраля 2021 в 22:52
2

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

public 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();
        }
    }
}
Peter Mortensen
13 мая 2021 в 00:01
0

Объяснение было бы в порядке. Например. в чем суть / идея? Чем он отличается от предыдущих ответов?

avatar
duffymo
21 ноября 2020 в 06:09
21

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

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); }
}
Falmarri
21 июля 2011 в 19:10
91

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

Kyle
17 июля 2012 в 19:08
3

Я согласен с @Falmarri. Я не вижу там утечки, вы просто создаете объекты. Вы, безусловно, можете «вернуть» только что выделенную память с помощью другого метода, называемого removeFromCache. Утечка - это когда вы не можете вернуть память.

duffymo
17 июля 2012 в 20:07
4

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

Falmarri
18 июля 2012 в 22:10
8

@duffymo: Но вопрос не об этом. Это не имеет ничего общего с простым использованием всей вашей памяти.

gyorgyabraham
29 ноября 2013 в 10:36
3

Совершенно неверно. Вы просто собираете кучу объектов в коллекцию карт. Их ссылки будут сохранены, потому что они есть на карте.

sceaj
28 ноября 2017 в 22:59
3

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

Ernest Friedman-Hill
3 января 2019 в 14:09
5

Комментарии, отвергающие этот ответ, довольно забавны, учитывая, что принятый ответ, набравший более 2000 голосов, говорит об одном и том же, хотя и в сильно запутанной форме. ThreadLocals не волшебство - это просто записи в Map, на которые указывает переменная-член в Thread.

kocka
27 августа 2021 в 08:10
0

Это то, что вызывает 99% реальных утечек памяти. Тот, кто не знает, в чем разница между картой и кешем.

avatar
meriton
21 ноября 2020 в 06:07
123

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

rds
22 июля 2011 в 16:26
5

И где тут утечка памяти?

meriton
23 июля 2011 в 13:26
31

@maniek: Я не имел в виду, что в этом коде обнаружена утечка памяти. Я процитировал его, чтобы показать, что иногда требуется неочевидный код, чтобы избежать случайного удержания объекта.

Koray Tugay
9 июня 2014 в 18:36
1

Что такое RangeCheck (index); ?

rents
27 июня 2015 в 02:22
7

Джошуа Блох привел этот пример в «Эффективной Java», показывающий простую реализацию стеков. Очень хороший ответ.

DGoiko
30 мая 2020 в 14:24
0

Но это не было бы НАСТОЯЩЕЙ утечкой памяти, даже если бы забыли. К элементу по-прежнему будет БЕЗОПАСНЫЙ доступ с помощью Reflection, он просто не будет очевиден и доступен напрямую через интерфейс List, но объект и ссылка все еще там, и к ним можно безопасно получить доступ.

Andy
4 февраля 2021 в 08:02
0

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

avatar
16 мая 2020 в 09:27
0

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

avatar
PlayTank
2 августа 2019 в 21:46
172

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

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

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

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

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

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

DaveC
3 июля 2011 в 17:59
26

Всякий раз, когда я задавал этот вопрос, я ищу довольно простой ответ - продолжайте увеличивать очередь, не закрывайте БД и т. Д., А не нечетные детали загрузчика классов / потока, подразумевают, что они понимают, что gc может и не может сделать для вас. Я полагаю, это зависит от работы, на которую вы собираетесь пройти собеседование.

avatar
Marko Pacak
18 июня 2018 в 07:28
14

Еще один способ создать потенциально огромные утечки памяти - хранить ссылки на Map.Entry<K,V> из TreeMap.

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


Реальный сценарий:

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

public static Map<String, Integer> pseudoQueryDatabase();

Если запрос вызывается много раз и для каждого запроса (т.е. для каждого возвращенного Map) вы где-то сохраняете Entry, объем памяти будет постоянно увеличиваться.

Рассмотрим следующий класс оболочки:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

Приложение:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

После каждого вызова pseudoQueryDatabase() экземпляры map должны быть готовы к сбору, но этого не произойдет, поскольку по крайней мере один Entry хранится где-то еще.

В зависимости от ваших настроек jvm приложение может аварийно завершить работу на ранней стадии из-за ошибки OutOfMemoryError.

На этом графике visualvm видно, как увеличивается объем памяти.

Memory dump - TreeMap

Этого не происходит с хешированной структурой данных (HashMap).

Это график при использовании HashMap.

Memory dump - HashMap

Решение? Просто сохраните напрямую ключ / значение (как вы, вероятно, уже делаете), вместо того, чтобы сохранять Map.Entry.


Я написал здесь более подробный тест.

avatar
Nicolas Bousquet
13 февраля 2018 в 07:27
228

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

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

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

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

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

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

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

ehabkost
22 июля 2011 в 06:12
35

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

SyntaxT3rr0r
22 июля 2011 в 11:49
0

@Nicolas Bousquet: «Утечка памяти действительно возможна» Большое спасибо. +15 голосов за. Отлично. Меня кричали здесь за то, что я констатировал этот факт, как предпосылку вопроса о языке Go: coderhelper.com/questions/4400311 У этого вопроса все еще есть отрицательные голоса :(

supercat
9 мая 2013 в 16:41
0

Сборщик мусора в Java и .NET в некотором смысле основан на предположении, что граф объектов, содержащих ссылки на другие объекты, такой же, как граф объектов, которые «заботятся» о других объектах. В действительности, возможно, что в ссылочном графе могут существовать ребра, которые не представляют «заботливые» отношения, и объект может заботиться о существовании другого объекта, даже если нет прямого или косвенного ссылочного пути (даже с использованием WeakReference) существует от одного к другому. Если бы в ссылке на объект был запасной бит, было бы полезно установить индикатор «заботится о цели» ...

supercat
9 мая 2013 в 16:47
0

... и система должна предоставлять уведомления (с помощью средств, аналогичных PhantomReference), если обнаружено, что объект не имеет никого, кто бы о нем заботился. WeakReference в чем-то близок, но перед использованием его необходимо преобразовать в сильную ссылку; если цикл GC происходит, пока существует сильная ссылка, цель будет считаться полезной.

nalply
18 апреля 2017 в 12:16
1

На мой взгляд, это правильный ответ. Мы написали симуляцию много лет назад. Каким-то образом мы случайно связали предыдущее состояние с текущим состоянием, создав утечку памяти. Из-за крайнего срока мы так и не устранили утечку памяти, а сделали ее «особенностью», задокументировав ее.

Jim Newpower
11 ноября 2020 в 16:38
1

Согласен с этим ответом. Чтобы немного вникнуть в голову интервьюера: среди, возможно, менее опытных разработчиков есть ощущение, что Java предотвращает утечки памяти (в отличие, например, от C / C ++). Итак, вопрос младшему разработчику: как создать утечку памяти в Java? Есть много способов, и большинство из них из-за простой невнимательности, хорошо проиллюстрированной выше.

avatar
27 ноября 2017 в 14:51
-7

Вот так!

public static void main(String[] args) {
    List<Object> objects = new ArrayList<>();
    while(true) {
        objects.add(new Object());
    }
}
Karan Khanna
19 апреля 2018 в 08:55
10

Это не пример утечки памяти. Вы просто пытаетесь занять всю память здесь своим списком. Утечка памяти - это когда устаревшие ссылки не могут быть удалены сборщиком мусора.

avatar
bestsss
16 февраля 2016 в 15:52
287

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

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

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

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

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

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

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

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

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

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

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

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

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

Удачи и спасения; утечки - зло!

leonbloy
9 июля 2011 в 01:54
26

Creating but not starting a Thread... Ура, несколько веков назад меня сильно укусил этот! (Java 1.3)

bestsss
9 июля 2011 в 06:52
0

@leonbloy, раньше было еще хуже, поскольку поток был добавлен прямо в группу потоков, отсутствие запуска означало очень серьезную утечку. Это не просто увеличивает количество unstarted, но это предотвращает разрушение группы потоков (меньшее зло, но все же утечка)

Lawrence Dol
8 ноября 2017 в 02:47
0

Спасибо! «Вызов ThreadGroup.destroy(), когда у ThreadGroup нет самих потоков ...» - невероятно тонкая ошибка; Я гонялся за этим часами и сбился с пути, потому что перечисление потока в моем управляющем графическом интерфейсе ничего не показало, но группа потоков и, предположительно, по крайней мере одна дочерняя группа не исчезли.

Lawrence Dol
9 ноября 2017 в 21:09
2

@bestsss: Мне любопытно, зачем вам убирать ловушку выключения, если она работает, ну, ну, при завершении работы JVM?

Lawrence Dol
11 сентября 2020 в 16:08
0

@ user253751: Это в контексте утечки памяти. В этом контексте невозможность удалить ловушку завершения работы после ее использования не является утечкой, поскольку JVM завершает работу. Однако, если вам нужно что-то сделать при выключении только временно, то не удаление этого и постоянное добавление новых обработчиков приведет к утечке.

avatar
Ron
16 февраля 2016 в 15:44
33

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

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

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

Paŭlo Ebermann
3 июля 2011 в 00:18
4

Просто чтобы придраться к вашему примеру кода: я думаю, что числа (или строки, начинающиеся с цифр) не допускаются в качестве имен элементов в XML.

jmiserez
18 апреля 2017 в 19:47
0

Обратите внимание, что это больше не верно для JDK 7+, где интернирование строк происходит в куче. См. Подробную информацию в этой статье: java-performance.info/string-intern-in-java-6-7-8

anubhs
2 июня 2020 в 17:03
0

Итак, я думаю, что использование StringBuffer вместо String решит эту проблему? не так ли?

avatar
pauli
16 февраля 2016 в 15:40
38

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

rds
22 июля 2011 в 16:34
1

Платформа Android дает пример утечки памяти, вызванной кешированием Bitmap в статическом поле View.

avatar
Vineet Reynolds
16 февраля 2016 в 15:38
136

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

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

Даже если драйвер JDBC должен реализовать finalize, во время финализации могут возникать исключения. Результатом является то, что любая память, связанная с теперь «спящим» объектом, не будет восстановлена, поскольку finalize гарантированно будет вызван только один раз.

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

Есть еще много примеров, которые вы можете вызвать, например,

  • Управление экземпляром List, когда вы только добавляете в список, а не удаляете из него (хотя вы должны избавляться от элементов, которые вам больше не нужны), или
  • Открытие Socket или File, но не закрытие их, когда они больше не нужны (аналогично приведенному выше примеру с классом Connection).
  • Синглтоны не выгружаются при остановке приложения Java EE. Очевидно, загрузчик классов, который загрузил одноэлементный класс, сохранит ссылку на этот класс, и, следовательно, одноэлементный экземпляр никогда не будет собран. При развертывании нового экземпляра приложения обычно создается новый загрузчик классов, а прежний загрузчик классов продолжает существовать благодаря синглтону.
Hardwareguy
21 июля 2011 в 16:23
103

Как правило, вы достигнете максимального лимита открытого соединения раньше, чем вы достигнете пределов памяти. Не спрашивайте меня, почему я знаю ...

chotchki
20 августа 2013 в 16:06
0

Драйвер Oracle JDBC печально известен тем, что делает это.

Michael Shopsin
22 апреля 2014 в 14:46
0

@Hardwareguy Я часто сталкивался с ограничениями соединений баз данных SQL, пока не помещал Connection.close в блок finally всех моих вызовов SQL. Для дополнительного удовольствия я вызвал некоторые долго выполняющиеся хранимые процедуры Oracle, которые требовали блокировок на стороне Java, чтобы предотвратить слишком много обращений к базе данных.

Aseem Bansal
22 сентября 2014 в 18:21
0

@Hardwareguy Это интересно, но на самом деле не обязательно, чтобы фактические ограничения на количество подключений были достигнуты для всех сред. Например, для приложения, развернутого на сервере приложений weblogic 11g, я видел утечки соединений в больших масштабах. Но из-за возможности сбора просочившихся соединений соединения с базой данных оставались доступными, пока происходили утечки памяти. Я не уверен во всех средах.

Bjørn Stenfeldt
31 августа 2017 в 08:07
0

По моему опыту, вы получаете утечку памяти, даже если закрываете соединение. Сначала вам нужно закрыть ResultSet и PreparedStatement. Был сервер, который неоднократно падал после нескольких часов или даже дней нормальной работы из-за OutOfMemoryErrors, пока я не начал это делать.

zero_yu
24 февраля 2018 в 22:55
0

Извините, я новичок в Java, поэтому мой вопрос может показаться глупым. Не могли бы вы объяснить: «Результатом является то, что, хотя память будет освобождена из-за сбора недоступных объектов, ресурсы (включая память), связанные с объектом Connection, могут просто не быть освобождены»? Вы имеете в виду, что память, выделенная для самого объекта соединения, будет освобождена, однако память, выделенная для ресурсов, связанных с объектом соединения, не будет освобождена? Что именно означают ресурсы? Спасибо!

lafual
3 ноября 2020 в 10:41
0

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

avatar
11 марта 2015 в 14:38
-5

В Java не существует утечки памяти. Утечка памяти - это фраза, заимствованная у C et al. Java занимается внутренним распределением памяти с помощью сборщика мусора. Имеется нерациональное использование памяти (т. Е. Оставление невыделенных объектов), но не утечка памяти .

avatar
bvdb
28 октября 2014 в 13:18
26

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

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

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

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

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

bvdb
3 мая 2021 в 15:02
0

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

avatar
5 июня 2014 в 21:54
7

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

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

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

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

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

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

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

Stefano Sanfilippo
21 июня 2015 в 16:40
0

Что касается последнего предложения, лучший способ победить зло - это хорошо его знать. Если вы хотите написать безопасное веб-приложение, вам следует ознакомиться с наиболее распространенными методами эксплойтов и уязвимостями, такими как SQL-инъекции или переполнение буфера. Точно так же, если вы хотите написать код без утечек, вы должны, по крайней мере, уметь описать наиболее распространенные способы утечки памяти, такие как потерянные указатели в C / C ++. Однако в Java определенно не так просто.

avatar
Puneet
7 марта 2014 в 21:29
22

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

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

TraderJoeChicago
20 сентября 2011 в 23:10
0

Это отстой. Вы должны проверить эту библиотеку ведения журнала, которая выделяет НУЛЬ памяти при записи в файл: mentalog.soliveirajr.com

sparc_spread
2 июля 2014 в 21:40
0

+1 Навскидку знаете, есть ли аналогичная проблема с MDC в slf4j / logback (продукты-преемники того же автора)? Я собираюсь глубоко погрузиться в источник, но сначала хотел проверить. В любом случае, спасибо за публикацию.

avatar
29 августа 2013 в 12:10
7

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

avatar
9 июля 2013 в 06:45
4

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

avatar
7 марта 2013 в 02:15
4

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

avatar
stones333
17 января 2013 в 10:58
-5

Вот очень простая программа на Java, у которой не хватит места

public class OutOfMemory {

    public static void main(String[] arg) {

        List<Long> mem = new LinkedList<Long>();
        while (true) {
            mem.add(new Long(Long.MAX_VALUE));
        }
    }
}
rds
17 января 2013 в 10:59
35

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

kritzikratzi
1 апреля 2013 в 20:04
3

также -1, а не утечка памяти, просто выделяется слишком много

avatar
12 сентября 2012 в 20:07
15

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

}

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

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

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

Здесь вы не столкнетесь с ошибкой OOM, поскольку ссылки не сохраняются, приложение будет продолжать работать независимо от того, насколько велико значение ИТЕРАЦИИ в приведенном выше примере. Потребление памяти вашим процессом (видимым в верхней части (RES / RSS) или в проводнике процессов) растет, если приложение не дойдет до команды ожидания. В приведенной выше настройке будет выделено около 150 МБ памяти.

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

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

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

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

avatar
22 июля 2011 в 08:05
16

Вы можете создать движущуюся утечку памяти, создав новый экземпляр класса в методе 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());
        }
    }
}
avatar
Jon Chambers
21 июля 2011 в 19:00
40

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

rds
22 июля 2011 в 16:45
4

Я бы не назвал это утечкой памяти, как таковой . Когда muchSmallerString освобождается (поскольку объект StringLeaker уничтожен), длинная строка также будет освобождена. То, что я называю утечкой памяти, - это память, которую невозможно освободить в этом экземпляре JVM. Однако вы сами показали, как освободить память: this.muchSmallerString=new String(this.muchSmallerString). С реальной утечкой памяти ничего не поделаешь.

Jon Chambers
22 июля 2011 в 18:42
2

@rds, это справедливо. Случай не intern может быть больше «сюрпризом для памяти», чем «утечкой памяти». .intern() Однако использование подстроки определенно создает ситуацию, когда ссылка на более длинную строку сохраняется и не может быть освобождена.

anstarovoyt
22 марта 2013 в 12:43
17

Метод substring () создает новую строку в java7 (это новое поведение)

Bananeweizen
4 октября 2017 в 16:46
0

Вам даже не нужно самостоятельно выполнять substring (): используйте средство сопоставления регулярных выражений, чтобы сопоставить крошечную часть огромного ввода, и переносить «извлеченную» строку в течение длительного времени. Огромный ввод сохраняется до Java 6.

avatar
21 июля 2011 в 17:41
9

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

Фрагмент javadoc для GC:

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

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

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

avatar
16 июля 2011 в 00:29
6

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

avatar
9 июля 2011 в 01:23
22

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

bestsss
9 июля 2011 в 06:54
2

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

cHao
16 августа 2013 в 20:33
2

«Слух» звучит так, будто кто-то наполовину прочитал о том, как работает генеральный сборщик мусора. Долгоживущие, но теперь недоступные объекты действительно могут оставаться и занимать место на какое-то время, потому что JVM выдвинула их из более молодого поколения, чтобы перестать проверять их каждый проход. Они намеренно избегают пустых пассов "очистить мои 5000 временных струн". Но они не бессмертны. Они по-прежнему имеют право на сбор, и если виртуальная машина привязана к оперативной памяти, она в конечном итоге выполнит полную очистку сборщика мусора и вернет себе эту память.

avatar
3 июля 2011 в 17:36
15

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

bestsss
5 июля 2011 в 19:38
10

Это неправда. finalize() не будет вызываться, но объект будет собран, когда больше не будет ссылок. Сборщик мусора тоже не «вызывается».

Tom Cammann
19 декабря 2012 в 11:36
1

Этот ответ вводит в заблуждение, метод finalize() может быть вызван JVM только один раз, но это не означает, что он не может быть повторно обработан сборщиком мусора, если объект был воскрешен, а затем снова разыменован. Если в методе finalize() есть код закрытия ресурса, этот код больше не запустится, это может вызвать утечку памяти.

avatar
3 июля 2011 в 06:49
10

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

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

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

avatar
Peter Lawrey
24 июня 2011 в 21:50
480

Проще всего использовать 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.
Peter Lawrey
24 июня 2011 в 16:20
0

@delnan, без указания hashCode () / equals () достаточно.

Donal Fellows
25 июня 2011 в 17:23
77

Фактически, вы можете удалить элементы из HashSet, даже если класс элемента получает hashCode и равен ему неверно; просто получите итератор для набора и используйте его метод удаления, поскольку итератор фактически работает с самими базовыми записями, а не с элементами. (Обратите внимание, что нереализованный hashCode / equals равен , а не , чтобы вызвать утечку; значения по умолчанию реализуют простую идентификацию объекта, поэтому вы можете получить элементы и удалить их в обычном режиме.)

corsiKa
26 июня 2011 в 00:13
2

@Donal, ваше последнее утверждение неверно. Вы не можете получить элемент, потому что у вас нет на него ссылки. Единственная ссылка на ключ, помещенный на карту, - это тот, который содержит сама карта. Только если вы пойдете BadKey myKey = new BadKey("key"); map.put(key,"value");, вы когда-нибудь сможете его достать. Вы правы, вы можете использовать итератор для его удаления, но вы не всегда можете сделать это со всеми структурами данных. Например, если вы не обнуляете ссылку в ответе @meriton, она будет потеряна навсегда. У вас нет доступа к резервному массиву, и итератор остановится перед ним.

Peter Lawrey
26 июня 2011 в 07:14
0

Единственный способ удалить элементы, когда equals / hashCode неверно, - это использовать Iterator.remove (), когда вы найдете совпадение с использованием другого метода сравнения.

Donal Fellows
26 июня 2011 в 09:56
1

@Total: Но я не рассматривал проблему всех структур данных; всегда можно сделать глупый код, который не удаляет файлы, несмотря на то, что их просят. (В этом случае правильнее всего сделать GC исходный код этого класса. Возможно, его автор тоже.) Я обращался к HashSet, где то, что я сказал, верно; независимо от того, насколько плохой является реализация элемента, вы все равно можете дать ему загрузку. (Я перепроверил исходный код.)

corsiKa
26 июня 2011 в 20:24
14

@Donal, что я пытаюсь сказать, наверное, я не согласен с вашим определением утечки памяти. Я бы рассмотрел (продолжая аналогию) вашу технику удаления итератора как капельницу под утечкой; утечка все еще существует независимо от поддона.

user541686
2 июля 2011 в 04:30
103

Я согласен, это , а не «утечка» памяти, потому что вы можете просто удалить ссылки на хэш-набор и подождать, пока сработает сборщик мусора, и готово! память возвращается.

SyntaxT3rr0r
22 июля 2011 в 11:43
0

@PeterLawrey: вы спросили меня, есть ли у меня «факты» в этом вопросе: coderhelper.com/questions/4400311 +130 голосов за примеры в этом вопросе и ответ bestssss в основном все факты, которые мне нужны. Ваш комментарий был язвительным и поддержал десять фанатиков Java. Мне все еще грустно, что мой вопрос на -1 (потому что такие люди, как вы, проголосовали против него или поддержали его язвительными комментариями).

Peter Lawrey
22 июля 2011 в 12:32
13

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

Peter Lawrey
22 июля 2011 в 12:37
0

@ SyntaxT3rr0r, В моем примере, как только вы сбросите карту, "утечка памяти" исчезнет. Здесь есть пример, когда память должна использоваться до тех пор, пока программа не существует. Невозможно реализовать эту функциональность без использования памяти и не может считаться ошибкой точно так же, как загрузка класса Object является утечкой памяти. Вы также должны прочитать комментарий Дариена.

SyntaxT3rr0r
22 июля 2011 в 12:50
0

@ Питер Лоури: если примеры не являются утечками памяти в том смысле, в каком это понимает программист C / C ++, тогда, возможно, мы сможем сделать вывод из контекста (контекст «Java и Java не имеют одинаковой утечки памяти C / C ++ ""), что мы говорим обо всех различных утечках памяти, которые дают bestssss , а также утечки Дэниела Прайдена и т. д.? Как известно, этот вопрос + ответы набирают более 800 голосов, и, похоже, в значительной степени принято, что, в конце концов, у Java есть утечки памяти. Где я (или ОП) упомянул об утечке памяти на языке C / C ++?

SyntaxT3rr0r
22 июля 2011 в 12:54
0

@ Питер Лоури: в течение многих лет люди говорили, что в Java нет утечек памяти, и это вводило людей в заблуждение, заставляя думать, что автоматизированный сборщик мусора означает, что они никогда больше не потеряют память. Это была наглая (и настойчивая) ложь, которая прижилась. К счастью, были отличные статьи (например, на DeveloperWorks и Artima ), раскрывающие заблуждение в этом клише: «Следовательно, утечки памяти Java - это не то же самое, что утечки памяти C / C ++. не утечки памяти », и теперь, что еще лучше, этот вопрос содержит ответы, показывающие настоящие утечки Java.

SyntaxT3rr0r
22 июля 2011 в 12:56
13

@ Питер Лоури: также, что вы думаете об этом: «В языке C нет ничего, что могло бы привести к утечке памяти, если вы не забудете вручную освободить выделенную память» . Как это будет с интеллектуальной нечестностью? В любом случае, я устал: последнее слово за тобой.

Peter Lawrey
22 июля 2011 в 13:51
0

@ SyntaxT3rr0r, в этом вопросе говорится об "утечках памяти" в контексте функций, которые потребляют память как часть нормальной работы. Использование вами «утечек памяти» указывает на ошибку, которая не должна потреблять память (отрицательный момент). то есть, если бы использовался только лучший язык, этих утечек памяти не произошло бы. Ваше заявление о том, что «Вот факты ... во многих программах Java есть (незаметные или нет) утечки памяти», трудно поддержать, поскольку люди даже не могут согласиться с тем, что означает утечка памяти в Java. Похоже, вы согласны с тем, что в соответствии со стандартом C / C ++ термин никогда не бывает .

Darien
22 июля 2011 в 18:23
8

@ SyntaxT3rr0r: Кажется, все ваши сообщения вращаются вокруг обвинений в "фанатизме", "вопиющей лжи" и "интеллектуальной нечестности" ... Если вы хотите ненавидеть Java, я уверен, что далеко отсюда есть какой-то форум, где вы можете рассердиться разглагольствовать гораздо легче, не загрязняя технический разговор.

Peter Lawrey
22 июля 2011 в 18:27
8

@ SyntaxT3rr0r, Поразмыслив, я думаю, у вас есть хороший аргумент, но вы апеллируете к эмоциям, а не к техническим аргументам.

Peter Lawrey
22 июля 2011 в 22:33
5

@ SyntaxT3rr0r, я написал более развернутый ответ на ваш интересный вопрос vanillajava.blogspot.com/2011/07/java-and-memory-leaks.html

avatar
Rogach
24 июня 2011 в 21:48
35

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

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

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

Joachim Sauer
24 июня 2011 в 16:15
24

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

Rogach
24 июня 2011 в 16:17
0

@Joachim Sauer - я имел в виду второй тип. Первое сделать довольно просто :)

Fabian Barney
24 июня 2011 в 16:28
6

«С чистой java это практически невозможно». Что ж, мой опыт - еще один, особенно когда дело доходит до реализации кешей людьми, которые не знают о подводных камнях здесь.

SyntaxT3rr0r
22 июля 2011 в 11:51
4

@Rogach: в основном есть +400 голосов за различные ответы людей с +10 000 репутацией, что показывает, что в обоих случаях Иоахим Зауэр прокомментировал, что это очень возможно. Так что ваше «почти невозможное» не имеет смысла.

avatar
24 июня 2011 в 16:18
70

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

user541686
2 июля 2011 в 04:31
15

Я не верю, что это «утечка». Это ошибка, и это связано с особенностями программы и языка. Утечка - это объект, висящий вокруг без каких-либо ссылок на него .

Bill the Lizard
10 июля 2011 в 02:30
31

@Mehrdad: Это только одно узкое определение, которое не полностью применимо ко всем языкам. Я бы сказал, что любая утечка памяти - это ошибка, вызванная плохим дизайном программы.

user541686
10 июля 2011 в 03:14
2

Я понимаю, что вы имеете в виду (и частично согласен с этим), но проблема в том, что если вы посмотрите на это с этой точки зрения, тогда возникает вопрос «как вы создаете утечку памяти в X?» становится бессмысленным, поскольку это возможно на любом языке. Так что, хотя это может быть правдой, на самом деле это не относится к Java, поэтому я не думаю, что это то, что мы называем здесь «утечкой».

Bill the Lizard
10 июля 2011 в 14:09
9

@Mehrdad: ...then the question of "how do you create a memory leak in X?" becomes meaningless, since it's possible in any language. Я не понимаю, как вы пришли к такому выводу. Есть меньше способов создать утечку памяти в Java по любому определению. Это определенно все еще актуальный вопрос.

Dan Rosenstark
11 июля 2011 в 00:16
1

@ Mehrdad, замечательный момент, никогда не думал об этом так. В некотором смысле утечка в Java невозможна, но объем памяти программ может непреднамеренно увеличиваться. В Objective-C эта проблема НЕ будет называться утечкой памяти.

31eee384
21 июля 2011 в 18:22
0

@Bill the Lizard: Я согласен - это все равно будет правильным вопросом, но он станет бесполезным. По-прежнему значимый, но бесполезный. Если утечки памяти существуют, когда у вас все еще есть ссылки на объекты, что-нибудь может считаться утечкой памяти. Например, вы запускаете Minesweeper, но он был изменен так, что вы не можете щелкать плитки. (Не знаю, может быть, вам нравится, как выглядит плата.) Поскольку данные под плитками теперь «больше не нужны», будет ли эта память утечкой? Думаю, что нет, но из вашего ответа следует, что так и будет.

Bill the Lizard
21 июля 2011 в 18:30
7

@ 31eee384: Если ваша программа хранит в памяти объекты, которые она никогда не сможет использовать, то технически это утечка памяти. Тот факт, что у вас есть более серьезные проблемы, на самом деле этого не меняет.

31eee384
21 июля 2011 в 18:50
0

@Bill the Lizard: Согласно wikipedia, утечка памяти в объектно-ориентированной программе технически «когда объект хранится в памяти, но не может быть доступен для работающего кода». В моем сценарии к объекту можно получить доступ, но его просто не будет. (en.wikipedia.org/wiki/Memory_leak)

Bill the Lizard
21 июля 2011 в 19:13
8

@ 31eee384: Если ты точно знаешь, что этого не будет, значит, этого не может быть. Программа в том виде, в котором она написана, никогда не будет обращаться к данным.

31eee384
21 июля 2011 в 20:46
0

@Bill the Lizard: К сожалению, похоже, это просто разница в определении. Я думаю, что первое определение утечки памяти в Википедии меня поддерживает - утечка памяти - это «когда компьютерная программа потребляет память, но не может вернуть ее в операционную систему». Программа, безусловно, может это сделать, и эти дополнительные «просочившиеся» данные в Minesweeper исчезнут, когда какой-либо класс, содержащий данные, будет собран сборщиком мусора. Я понимаю вашу точку зрения, и в широком смысле происходит утечка данных, однако я считаю термин «утечка памяти» чем-то более конкретным и более серьезной проблемой.

31eee384
21 июля 2011 в 20:54
0

@Bill the Lizard: у меня не было достаточно символов, чтобы сказать это в моем предыдущем комментарии, но я хочу сказать, что я согласен с вами, учитывая, как вы определяете утечку памяти. Учитывая расплывчатое определение, я бы сказал, что есть два типа утечек памяти: менее важные и более важные. Меньшие - это те, где память просто тратится. В больших из них память не извлекается до тех пор, пока JVM не будет уничтожена. Хотя вы рассматриваете оба типа утечек памяти, я считаю утечкой только последний.

ehabkost
22 июля 2011 в 06:09
3

@ 31eee384: если вы храните ссылку на объект и никогда не удаляете его, память не будет извлечена, пока JVM тоже не будет уничтожена. Некоторые люди не назовут это «настоящей утечкой памяти», потому что где-то есть ссылка; но если объект никогда не будет использоваться программой снова, и в то же время ссылка никогда не будет удалена программой, это так же важно, как "настоящие утечки памяти", которые люди ищут здесь как забавное упражнение. (или, что более важно, они встречаются чаще, чем крайние случаи, которые люди создают в своих ответах только для того, чтобы получить «настоящие» утечки памяти)

31eee384
22 июля 2011 в 15:00
0

@ehabkost: Да, я понимаю разницу. Однако мой пример не такой - допустим, данные хранятся в классе Sweeper. Когда вы запускаете новую игру, для ссылки Sweeper устанавливается значение NULL, экземпляр успешно собран с помощью сборщика мусора , а ссылке Sweeper назначается новый экземпляр. Утечки здесь нет. С другой стороны, если у вас был класс Vec2f и вам удалось случайно добавить каждый экземпляр в какой-то статический стек, который никогда не очищается, это действительно будет утечкой памяти. Я считаю более подходящим термин для неиграбельного тральщика «потраченная впустую» память, а не «утечка».

31eee384
22 июля 2011 в 15:04
0

@ehabkost: Другими словами, я тоже с тобой согласен! Но я думаю, что вы оба искажаете мой сценарий как нечто, чем не является. Другой, возможно, более простой случай - это игра с фатальной ошибкой, которая мешает вам пройти определенный этап. Будут ли дальнейшие этапы, недоступные, но существующие, считаться утечками памяти? Будет ли патч, исправляющий неисправный этап, «исправлением утечки памяти»? Я думаю, что использовать эту фразу в таком ключе немного глупо.

ehabkost
22 июля 2011 в 15:44
0

@ 31eee384: да, я бы не назвал ваш пример на стадии игры "утечкой памяти". Но я думаю, что разница в том, что считается «правильным поведением» в каждом случае: в вашем примере «Сапер» программа должна была позволить пользователю перейти к следующим этапам. В других случаях, если ваша программа хранит бесполезные ссылки, когда она должна была их отбросить, это так же плохо, как «утечка памяти».

ehabkost
22 июля 2011 в 15:45
0

@ 31eee384: переверните ваш пример: если у меня есть ошибка в моей игре на C, когда я по ошибке делаю «next_stage = NULL» и не позволяю пользователю двигаться дальше, это технически «настоящая утечка памяти», но это Было бы глупо называть это утечкой памяти, потому что правильным решением было бы позволить пользователю двигаться дальше, а не освобождать память.

ehabkost
22 июля 2011 в 15:45
0

@ 31eee384 давайте продолжим обсуждение в чате

Bill the Lizard
22 июля 2011 в 15:59
0

@ehabkost: Я думал примерно так же. Это технически утечка памяти, но есть более полезные способы подумать об этом, чтобы решить проблему.

31eee384
22 июля 2011 в 16:22
0

Я думаю, что это немного вышло из-под контроля ... Первоначально я отвечал на вопрос: «Каждый раз, когда вы сохраняете ссылки на объекты, которые вам больше не нужны, происходит утечка памяти». В частности, я думаю, что я понял, что «необходимость» означает нечто более абсолютное, чем вы имели в виду - перечитывая ваши ранние комментарии, Билл Ящер, я обнаружил, что действительно согласен с вами. (Я также обнаружил, что на самом деле проголосовал за несколько ваших комментариев, прежде чем начал этот беспорядок.)

Andy
4 февраля 2021 в 08:06
1

Босс: "Вы нашли утечку памяти?" Программист: «Ну, мы нашли ошибку, но технически это не утечка памяти ...» Босс: «Тогда почему программе не хватает памяти? Просто исправьте утечку памяти!»