Почему этот пример кода вызывает утечку памяти? - PullRequest
3 голосов
/ 16 мая 2010

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

Что касается моего объектно-ориентированного программирования, единственная кодовая строка, способная вызвать утечку памяти, будет

items=Arrays.copyOf(items,2 * size+1); 

Документация гласит, что элементы скопированы. Означает ли это, что ссылка копируется (и, следовательно, создается другая запись в куче), или сам объект копируется? Насколько я знаю, Object и, следовательно, Object [] реализованы как ссылочный тип. Таким образом, присвоение нового значения «items» позволит сборщику мусора обнаружить, что на старый «item» больше нет ссылок и, следовательно, его можно собрать.

На мой взгляд, этот пример кода не вызывает утечку памяти. Может ли кто-нибудь доказать, что я не прав? =)

import java.util.Arrays;
public class Foo  
{  
private Object[] items;  
private int size=0;  
private static final int ISIZE=10;

public Foo()  
{  
  items= new Object[ISIZE];  
}  

public void push(final Object o){  
  checkSize();  
  items[size++]=o;  
}  

public Object pop(){  
  if (size==0)  
    throw new ///...  
  return items[--size];  
}  
private void checkSize(){  
  if (items.length==size){  
    items=Arrays.copyOf(items,2 * size+1);  
  }  
}  
}

Ответы [ 10 ]

11 голосов
/ 16 мая 2010

Метод pop вызывает утечку памяти.

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

Представьте себе это:

{
    Object o = new Object();
    myQueue.add(o);
}

Теперь у вас есть только одна ссылка на этот объект - одна в массиве.

Позже вы делаете:

{
    myQueue.pop();
}

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

Таким образом, если вы заполните Очередь n количеством объектов, то у вас будет ссылка для этих n объектов.

Это утечка памяти , о которой вам говорили ваши учителя.

9 голосов
/ 16 мая 2010

Подсказка: утечка в методе pop. Подумайте, что происходит со ссылками на всплывающий объект ...

6 голосов
/ 16 мая 2010

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

Я думаю, что проф имеет в виду, что вы не обнуляете всплывающие элементы (другими словами, после того, как вы вернете items[--size], вам, вероятно, следует установить items[size] = null). Но когда экземпляр Foo выходит из области видимости, тогда все будет собрано. Так что это довольно слабое упражнение.

4 голосов
/ 16 мая 2010

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

Это правда, что образец никогда не освобождает память. Тем не менее, неизданная память всегда используется повторно. В этом случае это не совсем соответствует определению утечки памяти.

for(int i = 0; i < 1000; i++)
    foo.push(new Object());
for(int i = 0; i < 1000; i++)
    foo.pop();

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

Такое поведение вы можете увидеть во многих реализациях malloc и free (C) - когда вы освобождаете память, она фактически не возвращается в ОС, а добавляется в список, который будет возвращен при следующем вызове malloc. Но мы все еще не предполагаем, что свободная утечка памяти.

4 голосов
/ 16 мая 2010

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

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

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

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

4 голосов
/ 16 мая 2010

Этот пример обсуждается в Effective Java Joshua Bloch. Утечка происходит, когда появляются элементы. Ссылки продолжают указывать на объекты, которые вы не используете.

1 голос
/ 16 мая 2010

Рассмотрим эту демонстрацию:

Foo f = new Foo();
{
    Object o1 = new Object();
    Object o2 = new Object();
    f.push(o1);
    f.push(o2);
}

f.pop();
f.pop();

// #1. o1 and o2 still are refered in f.items, thus not deleted

f = null;

// #2. o1 and o2 will be deleted now

В Foo должны быть улучшены некоторые вещи, которые исправят это:

  1. В pop необходимо установить для записи items значение null.
  2. Вы должны ввести противоположное checkSize, что-то вроде shrinkSize, которое уменьшит массив (возможно, аналогично checkSize).
1 голос
/ 16 мая 2010

В методе pop () элемент по размеру (т.е. items [size-1]) не устанавливается в NULL. В результате все еще существует ссылка от объектов items на items [size-1], хотя размер был уменьшен на единицу. Во время GC элементы [size-1] не будут собираться, даже если на него не указывает никакой другой объект, что приводит к утечке памяти.

1 голос
/ 16 мая 2010

Я не собираюсь выкладываться, чтобы дать вам ответ, но посмотрите, что push (Object o) делает, что pop () не делает.

1 голос
/ 16 мая 2010

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

...