Мои Конспекты
Главная | Обратная связь


Автомобили
Астрономия
Биология
География
Дом и сад
Другие языки
Другое
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Металлургия
Механика
Образование
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Туризм
Физика
Философия
Финансы
Химия
Черчение
Экология
Экономика
Электроника

Уничтожайте устаревшие ссыпки (на объекты)



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

Рассмотрим следующую реализацию простого стека:

import java.util.*;

//Можете ли вы заметить "утечку памяти"?

public class Stack {

private Object[] elements;

private int size = 0;

public Stack(int initialCapacity) {

this.elements = new Object[initialCapacity];

}

public void push(Object e) {

ensureCapacity();

elements[size++] = e;

}

public Object pop() {

if (size==0)

throw new EmptyStackException();

Object result = elements[--size];

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

return result;

}

/**

* Убедимся в том, что в стеке есть место хотя бы еще

* для одного элемента. Каждый раз, когда

* нужно увеличить массив, удваиваем его емкость.

*/

private void ensureCapacity() {

if (elements.length == size) {

Object[] oldElements = elements;

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

System.arraycopy(oldElements, 0, elements, 0, size);

} }

 

public static void main(String[] args) {

Stack s = new Stack(0);

for (int i=0; i<args.length; i++)

s.push(args[i]);

for (int i=0; i<args.length; i++)

System.out.println(s.pop());

} }

 

 

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

Где же происходит утечка? Если стек растет, а затем уменьшается, то объекты, которые были вытолкнуты из стека, не могут быть удалены, даже если программа, пользующаяся этим стеком, уже не имеет ссылок на них. Все дело в том, что стек сохраняет устаревшие ссылки (obsolete reference) на объекты. Устаревшая ссылка ­это такая ссылка, которая уже никогда не будет разыменована. В данном случае уста­ревшими являются любые ссылки, оказавшиеся за пределами активной части массива элементов. Активная же часть стека включает в себя элементы, чей индекс меньше значения переменной size.

Утечка памяти в языках с автоматической сборкой мусора (или точнее, непредна­меренное сохранение объектов - unintentional object retention) весьма коварна. Если ссылка на объект была непреднамеренно сохранена, сборщик мусора не сможет удалить не только этот объект, но и все объекты, на которые он ссылается, и т. д. Если даже непреднамеренно было сохранено всего несколько объектов, многие и многие объекты могут стать недоступными сборщику мусора, а это может оказать большое влияние на производительность программы.

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

 

public Object рор()

if (size == О)

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

elements[size] = null; //Убираем устаревшую ссылку

геtuгп result;

}

 

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

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

 

 

 

производительность. Обнуление ссылок на объект должно быть не нормой, а исклю­чением. Лучший способ избавиться от устаревшей ссылки - вновь использовать пе­ременную, в которой она находилась, либо выйти из области видимости переменной. Это происходит естественным образом, если для каждой переменной вы задаете самую ограниченную область видимости (статья 29). Следует, заметить, что в современных реализациях JVM недостаточно просто выйти из блока, в котором определена пере­менная. Чтобы переменная пропала, необходимо выйти из соответствующего метода­-контейнера.

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

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

Другим распространенным источником утечки памяти являются КЭШи. Поместив однажды в кэш ссылку на некий объект, легко можно забыть о том, что она там есть, и держать ссылку в КЭШе еще долгое время после того, как она стала недействитель­ной. Возможны два решения этой проблемы. Если вам посчастливилось создать кэш, в котором запись остается значимой ровно до тех пор, пока за пределами КЭШа остают­ся ссылки на ее ключ, представьте этот кэш как WeakHashMap: когда записи устареют, они будут удалены автоматически. В общем случае время, на протяжении которого запись в КЭШе остается значимой. четко не оговаривается. Записи теряют свою значи­мость с течением времени. В таких обстоятельствах кэш следует время от времени очищать от записей, которыми уже никто не пользуется. Подобную чистку может вы­полнять фоновый поток (например, через АРI java.util. Тiтeг), либо это может быть побочным эффектом от добавления в кэш новых записей. При реализации второго подхода применяется метод removeEldestEntry из класса java.util.LinkedHashMap, включенного в версию 1.4.

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