Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



При переходе с языка программирования с ручным управлением памятью, такого как С или С++, на язык с автоматической очисткой памяти (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());

}   }

16

 

 

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

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

 

 

17

 

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

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

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

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

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

 

18

 


Поделиться:



Последнее изменение этой страницы: 2019-04-11; Просмотров: 213; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.012 с.)
Главная | Случайная страница | Обратная связь