Статьи

Почему ваше программное обеспечение стареет?

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

Но давайте начнем с того, что Википедия должна была сказать по этому поводу:

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

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

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

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

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

  • Утечки памяти (наш нынешний хлеб с маслом)
  • Блокировка конфликтов
  • Неизданные файловые дескрипторы
  • Память / пространство подкачки
  • Повреждение данных
  • Фрагментация пространства хранения
  • Накопление ошибок округления

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

Утечки памяти . Это наш текущий хлеб с маслом — каждый день я сталкиваюсь с десятками различных ситуаций, когда приложения страдают от утечек. Фактически, из нашего текущего набора данных из нескольких тысяч приложений мы видим, что примерно 50% приложений содержат одно. Следующий пример иллюстрирует случай.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public class Calc {
  Map cache = new HashMap();
 
  public int square(int i) {
     int result = i * i;
     cache.put(i, result);
     return result;
  }
 
  public static void main(String[] args) throws Exception {
     Calc calc = new Calc();
     while (true)
        System.out.println("Enter a number between 1 and 100");
        int i = readUserInput(); //not shown
        System.out.println("Answer " + calc.square(i));
     }
  }
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
class Account {
 double balance;
 int id;
 
 void withdraw(double amount){
    balance -= amount;
 }
 
 void deposit(double amount){
    balance += amount;
 }
 
  void transfer(Account from, Account to, double amount){
       sync(from);
       sync(to);
          from.withdraw(amount);
          to.deposit(amount);
       release(to);
       release(from);
   }
}

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

1
2
3
4
5
6
7
Properties p = new Properties();
try {
   p.load(new FileInputStream(“my.properties”));
} catch (Exception ex) {}
finally {
  //no, i will NOT close the stream
 }

Память / пространство подкачки переполнено . Современные ОС стремятся быстро вывести на экран память, которая не была затронута некоторое время. Таким образом, вы можете столкнуться с проблемами, когда у вас кончится физическая память, и ОС начнет менять вашу кучу. Вещи становятся все хуже и хуже из-за сборки мусора — Full GC требует, чтобы JVM прошел по графу объектов, чтобы идентифицировать каждый достижимый объект для обнаружения мусора. При этом он будет касаться каждой страницы в куче приложения, вызывая подкачку и выгрузку страниц из памяти.

К счастью, в современных JVM эффекты уменьшаются по нескольким причинам, например:

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

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

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

Ссылка: Почему ваше программное обеспечение стареет? от нашего партнера JCG Никиты Сальникова Тарновского в блоге Plumbr Blog .