Исследование практического применения средства профилирования NetBeans


1. Введение

Средство профилирования NetBeans основано на разработанной Sun технологии профилирования, известной как Jfluid. Jfluid — отличное средство. Оно позволяет обнаруживать утечки памяти и узких мест производительности в приложениях Java. Технология Jfluid находится в процессе интегрирования в NetBeans. На данный момент ей можно воспользоваться в среде IDE NetBeans 4.0. В этой статьи использована сборка средства профилирования третьего этапа разработки. Задача статьи заключается в передаче опыта использования средства профилирования для совершенствования приложений. Автор, однако, может только указать на дверь. Войти в нее читателю придется самостоятельно.

Если читатель не обладает опытом использования средства профилирования NetBeans, рекомендуется сначала перейти по следующему адресу URL. Настройке средства профилирования посвящено несколько документов.


2. Способ определения утечки памяти

Ниже приведены принципы определения утечки памяти с помощью средства профилирования. Определение утечки памяти выполняется поэтапно.

  • Контролируйте размер "кучи" и тенденции к его изменению.
  • Если используемый размер "кучи" продолжает увеличиваться, проверьте, какой объект занимает больше всего объема размер "кучи" виртуальной машины.
  • Обратите внимание, где распределяется и освобождается память для объекта.
  • Проверьте правильность распределения и освобождения памяти проекта в коде по месту.

Как выполнить описанные выше действия с помощью средства профилирования? Рассмотрим несколько графиков средства профилирования.

График "кучи" в окне "Средство профилирования"

Сначала рассмотрим окно "Средство профилирования" под другими окнами среды IDE NetBeans. На левом рисунке показан график размера "кучи" и занятого размера "кучи". На нем можно наблюдать изменение размера "кучи" и занятого размера "кучи" в ходе выполнения приложения. Если занятый объем "кучи" увеличивается с течением времени, то, вероятно, имеет место утечка памяти.

Обнаружение утечек памяти

На рисунке выше кратко объясняется использование памяти в виртуальной машине. В этом случае занятый объем "кучи" на левом графике относительно стабилен. Он не растет со временем.

График активных объектов

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

В примере "График памяти активных объектов" выше наибольший объем памяти занят объектом HashMap$Entry. Однако число в поле "Поколения" невелико. Сложно сделать вывод о наличии или отсутствии утечки памяти. Придется продолжать выполнение приложения и следить за использованием памяти.

Обратный граф вызовов

Если какой-либо объект вызывает подозрения, обратимся к обратному графу вызовов. Он позволяет определять, где выделены объекты, занимающие память. Таким образом можно получить представление о том, что следует искать в коде Java.

На рисунке выше показан обратный граф вызовов для объекта "char". Граф показывает, что "char" выделяется в методе StringBuffer.expandCapacity(int). Обратный граф вызовов позволяет определить, где выделяется рассматриваемый объект. После этого можно обратиться к исходному коду по месту.


3. Практическое исследование: обнаружение утечки памяти

В этом разделе рассмотрим обнаружение утечки памяти с помощью средства профилирования. Средство профилирования NetBeans обладает функциональными возможностями профилирования активного состояния объектов (путем записи создания и сборки мусора для объекта). Отображается статистика выделения и освобождения объектов, по которой можно обнаружить проблемы с памятью.

Пример кода

Для демонстрации обнаружения утечки памяти с помощью средства профилирования воспользуемся следующим примером кода.

[Пример кода TestHash]


/*
 * Main.java
 *
 * Created on 2005/01/06, 11:00
 */

package memoryleak;

import java.util.*;
import java.io.*;

/**
 *
 * @author root
 */
public class Main {
    Hashtable hashtable = new Hashtable();
    Square sq[] = new Square[10];
   
    /** Создание экземпляра Main */
    public Main() {        
        
    }
    
    /**
     * @param args параметры командной строки
     */
    public static void main(String[] args) {
        // TODO прикладная логика
        Main testhash = new Main();   
        for(int i=0; i<100000; i++) {
            testhash.create(i);  
            testhash.use(i);    
            testhash.release();
        }
    }
    
    // Создание объектов Square.
    void create(int i) {
        for(int j=0; j<10; j++) {
            long index = j+i*10;
            sq[j] = new Square(index);
            hashtable.put(sq[j].num, sq[j]);
        }
    }
    
    // Использование объектов Square.
    void use(int i) {
        for(int j=0; j<10; j++) {
            System.out.print(((Square)(hashtable.get(sq[j].num))).square + " ");
        }
        System.out.println();
    }
    
    void release() {
        for(int j=0; j<10; j++) {
            sq[j] = null;
        }
    }
    
}

class Square {
    String num;
    String square;
    public Square(long num) {
        this.num = new Long(num).toString();
        this.square = new Long(num*num).toString();
    }
}

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

  • Создание объектов Square.
  • Сохранение созданных объектов Square в Hashtable.
  • Освобождение объектов Square после использования.

В конечном итоге объекты Square освобождаются, поэтому кажется, что утечки памяти нет.

Проблема: OutOfMemoryError

Теперь выполним исходный код. В случае отсутствия проблем должны быть выведены квадраты чисел с 1 по 999999. Вот результат выполнения.

[Результат выполнения]


0 1 4 9 16 25 36 49 64 81 
	:
	вырезано
	:
179411544900 179412392041 179413239184 179414086329 179414933476 
179415780625 179416627776 179417474929 179418322084 179419169241 
Exception in thread "main" java.lang.OutOfMemoryError

К сожалению, приложение завершается с исключительной ситуацией OutOfMemoryError.

Использование средства профилирования

Почему приложение завершается с исключительной ситуацией OutOfMemoryError? Нетрудно понять, что приложение исчерпало "кучу" виртуальной машины, поскольку выдается ошибка OutOfMemoryError. Однако как это произошло, и какой объект вызвал ошибку?

Пришло время обратиться к средству профилирования NetBeans. С его помощью можно выполнить профилирование выделения и освобождения объектов. Для запуска средства профилирования выберите главный класс приложения в окне "Проводник" и выберите "Профилирование -> Профилирование файла..."

Затем выберите "Анализ использования памяти". Выберите "Записывать создание и сборку мусора объектов".

График "кучи" в окне "Средство профилирования"

Приложение запускается вместе со средством профилирования. Для просмотра статистики профилирования выберите пункт меню "Профилирование -> Получить текущие результаты". В первую очередь обратите внимание на окно "Средство профилирования" внизу общего окна. В графике "Куча" в окне "Средство профилирования" видно, что используемый объем "кучи" (показан фиолетовым цветом) продолжает расти. Таким образом, вероятно, имеет место утечка памяти.

[Рисунок текущих результатов]

Общее окно Netbeans

График активных объектов

Затем рассмотрим "График активных объектов" на рисунке выше. Большая часть объема памяти занята четырьмя объектами. Это объекты char[], String, Hashtable$Entry и Square. Затем рассмотрим поле "Поколения" на рисунке выше. Значение этого поля фактически является признаком утечки памяти. Если это значение стабильно растет, вероятно, имеет место утечка памяти. Для всех четырех объектов значение в поле "Поколения" высокое. 51. Значительный контраст с остальными объектами, для которых значение равно 1. Высокое значение заставляет подозревать, что объекты не освобождаются надлежащим образом, и имеет место утечка памяти.

Обратный граф вызовов

Теперь рассмотрим обратный граф вызовов для объекта "char" в верхней части списка. Для этого достаточно дважды щелкнуть элемент объекта "char".

[Обратный граф вызовов объекта char]

Общее окно Netbeans

В соответствии с обратным графом вызовов некоторые объекты "char" созданы в String.<init>, но не освобождены. Поскольку объект "char", созданный в Square.<init>, обладает таким же значением полей "Активные объекты" и "Выделенные объекты". Теперь рассмотрим обратный граф вызовов объекта "String".

[Обратный граф вызовов объекта String]

Общее окно Netbeans

Ситуация такая же, как и с объектом "char". Некоторые из объектов "String" созданы в Long.toString(), но не освобождены. Количество "выделенных" и "активных" объектов "String", созданных в Long.toString(), одинаково. Почему эти объекты не освобождены? Подсказка кроется в списке активных объектов. В этом списке объект "Hashtable.$Entry" также обладает высоким значением поля "Поколения". Объект "Hashtable.$Entry" остается активным, пока не будет вызван метод Hashtable.remove(). В объекте "Hashtable.$Entry" содержатся объекты, сохраненные в Hashtable. Следовательно, можно предположить, что объекты, сохраненные в Hashtable, не освобождены должным образом.

Рассмотрим код по месту.

Затем перейдем к исходному коду программы-примера.

[Отрывок исходного кода]


    void release() {
        for(int j=0; j<10; j++) {
            sq[j] = null;
        }
    }

Отлично! Мы вплотную приблизились к корню проблемы. Объекты "Square" совершенно правильно устанавливаются в "null". Однако мы, кажется, забыли освободить эти объекты "Square" в Hashtable. Все дело в Hashtable. В объекте "Square" есть объект "String". Объекты "String" в объектах "Square", очевидно, остаются в "куче". Для решения проблемы изменим метод "release" в исходном коде следующим образом.

[Измененный исходный код]


    void release() {
        for(int j=0; j<10; j++) {
            hashtable.remove(sq[j].num);  /* добавлено */
            sq[j] = null;
        }
    }

Теперь выполним исправленную программу.

[Результат]


0 1 4 9 16 25 36 49 64 81 
 :
вырезано
 :
999980000100 999982000081 999984000064 999986000049 999988000036 
999990000025 999992000016 999994000009 999996000004 999998000001 

Ура! Программа завершилась без ошибок. Функциональная возможность контроля использования памяти, предоставляемая средством профилирования NetBeans, позволяет глубже разобраться в утечках памяти.


4. Заключение

Рассмотрена методология и пример практического применения средства профилирования NetBeans для обнаружения утечек памяти. Опробуйте последний выпуск средства профилирования NetBeans. Это средство позволяет анализировать проблемы с выделением памяти в виртуальной машине. Надеемся, что средство профилирования NetBeans пополнит набор средств каждого разработчика.


By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2013, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo