Статьи

Stacktraces говорят правду.

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

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

  • java.lang.OutOfMemoryError: невозможно создать новый собственный поток
  • java.io.IOException: слишком много открытых файлов в системе
  • java.lang.OutOfMemoryError: пространство кучи Java

Все примеры проиллюстрированы простыми фрагментами кода, облегчающими понимание основной проблемы.

Слишком много тем

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
static void createTooManyThreads() {
    try {
        for (int i = 0; i < TOO_MANY; i++) {
            Thread t = new Thread(new Runnable() {
 
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    } catch (OutOfMemoryError e) {
        e.printStackTrace();
    }
}

В приведенном выше коде мы продолжаем запускать потоки, пока не достигнем системного предела и не встретим сообщение «java.lang.OutOfMemoryError: не в состоянии создать новый собственный поток» . Что плохого в понимании того, что проблема связана с исчерпанием ограничения потока? Давайте ближе рассмотрим трассировку стека:

1
2
3
4
5
java.lang.OutOfMemoryError: unable to create new native thread
    at java.lang.Thread.start0(Native Method)
    at java.lang.Thread.start(Thread.java:693)
    at eu.plumbr.demo.MisleadingStacktrace.createTooManyThreads(MisleadingStacktrace.java:34)
    at eu.plumbr.demo.MisleadingStacktrace.main(MisleadingStacktrace.java:16)

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

Но давайте посмотрим на ту же проблему — потребление ресурсов с другой точки зрения:

Слишком много открытых файлов

Опять же, давайте начнем с примера кода:

01
02
03
04
05
06
07
08
09
10
11
12
static void createTooManyFiles() {
    try {
        for (int i = 0; i < TOO_MANY; i++) {
            File f = new File(PATH_TO_FILE + i + ".txt");
            f.createNewFile();
            OutputStream s = new FileOutputStream(f);
            s.write(1);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

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

1
2
3
4
5
java.io.IOException: Too many open files in system
    at java.io.UnixFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:947)
    at eu.plumbr.demo.MisleadingStacktrace.createTooManyFiles(MisleadingStacktrace.java:45)
    at eu.plumbr.demo.MisleadingStacktrace.main(MisleadingStacktrace.java:17)

Та же самая проблема теперь замаскирована по-другому — мы получаем сообщение о том, что я сейчас слишком много пытался открыть один файл, но — кто открыл другие файлы, чтобы подчеркнуть JVM до такой степени, что он не может завершить выполнение?

Если вы все еще не уверены — взгляните на третий пример нашего нынешнего хлеба с маслом:

Слишком много памяти занято

Пример кода снова прост — мы берем структуру данных и продолжаем увеличивать ее, пока доступная куча не будет исчерпана:

01
02
03
04
05
06
07
08
09
10
static void createTooLargeDataStructure() {
    try {
        List l = new ArrayList();
        for (int i = 0; i < TOO_MANY; i++) {
            l.add(i);
        }
    } catch (OutOfMemoryError e) {
        e.printStackTrace();
    }
}

Запуск кода дает вам печально известное сообщение java.lang.OutOfMemoryError: пространство кучи Java . Что, опять же, трудно интерпретировать, если рассматриваемая структура данных была заполнена в различных возможных местах исходного кода.

Я знаю, что все коллеги-разработчики теперь беспомощно пожимают плечами, потому что в их мире все вышеперечисленное просто приняло бы форму ошибки, но — если бы мы зашли так далеко, почему бы нам не добиться большего? Я уверен, что мы можем, и вот что мы собираемся сделать — найти узкие места производительности для вас.

Если вас интересует полный пример кода, его можно скачать здесь . Код запускался на моем Macbook Pro с OS X 10.9 на JDK 7u25. После этого убедитесь, что вы не пропустите будущие обновления интересного контента, и подпишитесь на нашу ленту Twitter .

Справка: Stacktraces говорят правду. Но не вся правда. от нашего партнера JCG Никиты Сальникова Тарновского в блоге Plumbr Blog .