Знищуйте застарілі ссипкі (на об’єкти)

При переході з мови програмування з ручним керуванням пам'яттю, такого як С або С ++, на мову з автоматичним очищенням пам'яті (garbage-collect - "збір!<а мусора") ваша работа как программиста существенно упрощается благодаря тому обстоятельству, что ваши объекты автоматически утилизируются, как только вы перестаете их использовать. Когда вы впервые сталкиваетесь с этой особенностью, то воспринимаете ее как волшебство. Легко может создаться впечатление, что вам больше не нужно думать об управлении’ памятью, но это не совсем так.

Розглянемо наступну реалізацію простого стека:

// Чи можете ви помітити "витік пам'яті"?

public class Stack

private Object [] elements;

private int size = 0;

public Stack (int initialCapacity)

this.elements = new Object [initialCapacity];

public void push (Object e)

public Object pop ()

throw new EmptyStackException ();

Object result = elements [-size];

elements [size] = null; // Eliminate obsolete reference

* Переконаємося в тому, що в стеці є місце хоча б ще

* Для одного елемента. Кожен раз, коли

* Потрібно збільшити масив, подвоюємо його ємність.

private void ensureCapacity ()

if (elements.length == size)

Object [] oldElements = elements;

elements = new Object [2 * elements.length + 1];

public static void main (String [] args)

Stack s = new Stack (0);

for (int i = 0; i

for (int i = 0; i

У цій програмі немає помилок, які кидалися б в очі. Ви можете ретельно протестувати її, будь-яке випробування вона пройде з успіхом, але в ній все ж прихована одна проблема. У цій програмі є "витік пам'яті", яка може тихо проявлятися у вигляді зниження продуктивності в зв'язку з посиленою роботою збирача сміття, або у вигляді збільшення розміру використовуваної пам'яті. В крайньому випадку подібна витік пам'яті в хибному напрямку підкачування сторінок з диска і навіть до аварійного завершення програми з діагностикою OutOfMemoryError, хоча подібні відмови зустрічаються відносно рідко.

public Object рор ()

throw new EmptyStackException (); Object result = elements [-size];

elements [size] = null; // Прибираємо застарілу посилання

Обнулення застарілих посилань дає і інша перевага: якщо згодом хтось помилково спробує разименовать будь-яку з цих посилань, програма негайно завершиться з діагностикою NullРоiпtегЕхсерtiоп замість того, щоб спокійно виконувати неправильну роботу. Завжди вигідно виявляти помилки fIрограммірованія настільки швидко, наскільки це можливо.

Коли програмісти вперше стикаються з подібною проблемою, вони починають перестраховуватися, обнулити всі посилання на об'єкти, лише тільки програма закінчує роботу з ними. Це не потрібно, оскільки захаращує програму і знижує її

Так коли ж слід обнуляти посилання? Яка особливість класу Stack зробила його сприйнятливим до витоку пам'яті? Клас Stack керує своєю пам'яттю. Пул зберігання складається з масиву елементів (причому його осередками є посилання на об'єкти, а не самі об'єкти). Як зазначалося вище, елементи в активній частині масиву вважаються зайнятими, в решті - вільними. Складальник сміття цього знати ніяк не може, і для нього все посилання на об'єкти, що зберігаються в масиві, в рівній 'ступеня дійсні. Тільки програмісту відомо, що неактивна частина масиву не потрібна. Повідомити про це збирачеві сміття програміст може, лише вручну обнуляючи елементи масиву в міру того, як вони переходять в неактивними частина масиву.

Взагалі кажучи, якщо який-небудь клас починає керувати своєю пам'яттю, програміст повинен подумати про витік пам'яті. Як тільки елемент масиву звільняється, будь-які посилання на об'єкти, що були в цьому елементі, необхідно обнуляти.

Іншим поширеним джерелом витоку пам'яті є кеші. Помістивши одного разу в кеш посилання на якийсь об'єкт, легко можна забути про те, що вона там є, і тримати посилання в кеші ще довгий час після того, як вона стала недійсною. Можливі два рішення цієї проблеми. Якщо вам пощастило створити кеш, в якому запис залишається значущою рівно до тих пір, поки за межами кешу залишаються посилання на її ключ, уявіть цей кеш як WeakHashMap: коли записи застаріють, вони будуть видалені автоматично. У загальному випадку час, протягом якого запис в кеші залишається значущою. чітко не обмовляється. Записи втрачають свою значимість з плином часу. В таких обставинах кеш слід час від часу очищати від записів, якими вже ніхто не користується. Подібну чистку може виконувати фоновий потік (наприклад, через АРІ java.util. Тiтeг), або це може бути побічним ефектом від додавання в кеш нових записів. При реалізації другого підходу застосовується метод removeEldestEntry з класу java.util.LinkedHashMap, включеного в версію 1.4.

Оскільки витік пам'яті зазвичай не виявляє себе у вигляді очевидного збою, вона може залишатися в системі, роками. Як правило, виявляють її лише в результаті ретельної інспекції програмного коду або за допомогою інструменту налагодження, відомого як профілювальник (heap profiler). Тому дуже важливо навчитися передбачати проблеми, схожі на цю, до того, як вони виникнуть, і попереджати їх появу.

Джерело: Джошуа Блох, Java TM Ефективне програмування, Видавництво «Лорі»