четверг, 1 марта 2012 г.

Использование памяти в JAVA (Часть 2)

Как я писал в предыдущей статье "Использование памяти в JAVA (Часть 1)", для более эффективного получения данных тестирования, я решил создать отдельный класс.  Лучшим примером для меня показалось создать класс, содержащий в себе различные типы данных, нежели если это будет класс с однотипными элементами. В качестве членов класса я использовал типы данных, которые чаще всего применяются для написания программ. Некоторые типы преднамеренно не стал включать (например, тип short), потому что практически ими никогда не пользуюсь. Всем членам класса я установил модификатор private, их "геттерам" и "сеттерам" - public.

public class TestClass {
    private byte      fBt;
    private Byte      fByte;
    private char      fChr;
    private Calendar  fCld;
    private float     fFlt;
    private Float     fFloat;
    private Date      fDt;
    private double    fDbl;
    private Double    fDouble;
    private int       fInt;
    private Integer   fInteger;
    private long      fLng;
    private Long      fLong;
    private String    fStr;

    public TestClass() {
    }

/**
 * далее идут опеределения "гетеров" и "сетеров"
 */
}

Последовательность строк в объекте StringArrayFactory, показанная в первой части статьи, создает строковые данные тех типов, которые созданы в классе TestClass. Для наглядности, я разместил их в обоих случаях в одинаковой последовательности. Тем самым можно будет узнать, всегда ли лучше применять объявление строго типизированного класса? Или всё таки можно обойтись чем то более простым. Конечно, многие сразу же без раздумий скажут, что класс всегда лучше массива строк хотя бы потому, что каждому отдельному члену класса будет соответствовать свой тип и для этого не понадобится выносить мозг прогамме на конвертацию из одного типа данных в другой. И я с этим так же полностью согласен. Тем не менее, что одному хорошо, не всегда другому является приемлемым. Пусть каждый сам для себя выбирает какой путь реализации для него более пригодный для использования. В данном случае, меня интересуют только цифры.

На основе этого класса создаем Factory, который вернёт нам созданный объект, учитывая тот момент, что члены класса являющиеся объектами должны создаваться динамически.

public class TestClassFactory implements ObjectFactory {
    public Object makeObject() {
        TestClass tc = new TestClass();
        tc.setfBt((byte)12);
        tc.setfByte(new Byte((byte)12));
        tc.setfChr('a');
        tc.setfCld(Calendar.getInstance());
        tc.setfDbl(123.45);
        tc.setfDouble(new Double(123.45));
        tc.setfDt(new Date());
        tc.setfFloat(new Float(123.45));
        tc.setfFlt(123.45f);
        tc.setfInt(123);
        tc.setfInteger(new Integer(123));
        tc.setfLng(1234567891234L);
        tc.setfLong(new Long(1234567891234L));
        tc.setfStr(new String("Field"));
        return tc;
    }
}

В итоге, при создании объекта, появился следующий результат:

Factories.TestClassFactory produced Factories.TestClass which took 672 bytes

Конечно же, вряд ли на практике может понадобиться подобный класс, но тем не менее размер впечатляет. Но не мудрено иметь такой размер: одни Calendar и Date чего стоят! Так что вполне приемлемый размер. Посмотрим, как он себя поведет при дальнейшем использовании.

java.lang.reflect.Field и java.lang.reflect.Method

Никогда бы не подумал, что мне придётся так часто использовать рефлексию в своих программах. Раньше этого как то не требовалось и даже не приходилось думать в эту сторону. В JAVA приложениях рефлексия применяется достаточно часто. И более частой задачей является получение списка членов и public-методов определенного класса, когда он ещё не объявлен. При том, этот список возвращается ввиде массива, которые получаются вызовом соответствующих функций: getDeclaredFields() для членов класса и getMethods() для методов. Последний за частую применяется для получения "геттеров" и "сеттеров" класса по известному имени члена класса. В какой нибудь из последующих статей я постараюсь осветить и показать пример этого использования.

И так как данное применение имеет место быть, я не мог оставить их без внимания. А учитывая, что класс у меня уже для этого создан, осталось только создать нужный Factory и посмотреть на результат.

public class FieldFactory implements ObjectFactory {
    public Object makeObject() {
        try {
            return Class.forName("Factories.TestClass").getDeclaredFields();
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return 1;
    }
}

public class MethodsArrayFactory implements ObjectFactory {
    public Object makeObject() {
        try {
            return Class.forName("Factories.TestClass").getMethods();
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return 1;
    }
}

Первый нам вернет массив членов класса, второй массив методов класса. Получаем соответствующие результаты:

Factories.FieldsArrayFactory produced [Ljava.lang.reflect.Field; which took 1080 bytes
Factories.MethodsArrayFactory produced [Ljava.lang.reflect.Method; which took 3424 bytes

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

java.util.Vector

Следующей моей задачей было определить, что первее: курицо или яйцо что лучше использовать: Vector? Или ArrayList, который якобы пришёл на смену Vector-а? На смену вроде то и пришёл, только по предыдущим тестам, которые провел Heinz и которые я проверил сам, они ничем друг от друга не отличаются. Причем методы их так же практически одинаковы. Тогда воспользуемся ими в боевой обстановке. Для этого как всегда создадим 10000 копий объекта TestClass для каждого из этих объектов. Vector будет иметь такой вид:

public class VectorTCFactory implements ObjectFactory {
    public Object makeObject() {
        Vector result = new Vector(10000);
        TestClass tc;
        for (int i = 0; i < 10000; i++) {
            tc = new TestClass();
            tc.setfBt((byte)12);
            tc.setfByte(new Byte((byte)12));
            tc.setfChr('a');
            tc.setfCld(Calendar.getInstance());
            tc.setfDbl(123.45);
            tc.setfDouble(new Double(123.45));
            tc.setfDt(new Date());
            tc.setfFloat(new Float(123.45));
            tc.setfFlt(123.45f);
            tc.setfInt(123);
            tc.setfInteger(new Integer(123));
            tc.setfLng(1234567891234L);
            tc.setfLong(new Long(1234567891234L));
            tc.setfStr(new String("Field"));
            result.add(tc);
        }
        return result;
    }
}

ArrayList своим содержанием так же не будет отличаться

public class ArrayListTCFactory implements ObjectFactory {
    public Object makeObject() {
        ArrayList result = new ArrayList(10000);
        TestClass tc;
        for (int i = 0; i < 10000; i++) {
            tc = new TestClass();
            tc.setfBt((byte)12);
            tc.setfByte(new Byte((byte)12));
            tc.setfChr('a');
            tc.setfCld(Calendar.getInstance());
            tc.setfDbl(123.45);
            tc.setfDouble(new Double(123.45));
            tc.setfDt(new Date());
            tc.setfFloat(new Float(123.45));
            tc.setfFlt(123.45f);
            tc.setfInt(123);
            tc.setfInteger(new Integer(123));
            tc.setfLng(1234567891234L);
            tc.setfLong(new Long(1234567891234L));
            tc.setfStr(new String("Field"));
            result.add(tc);
        }
        return result;
    }
}

Получаем соответствующие результаты тестов:

Factories.VectorTCFactory produced java.util.Vector which took 6759368 bytes
Factories.ArrayListTCFactory produced java.util.ArrayList which took 6759360 bytes

М-да... Когда я увидел результат StringArrayTCFactory, я "завязал узелок на память", что подобное создание и хранение переменных если не противопоказано, то должно хотя бы использоваться в самых крайних случаях, когда никакого другого подручного средства нет. Но я сильно сомневаюсь, что такие случаи возможны. Однако, результаты тестов с VectorTCFactory и ArrayListTCFactory дали мне повод сомневаться в этом заключении. А так ли плох массив строк? Конечно, если на карту поставлен вопрос только об использовании памяти и ни чего другого учитывать не нужно, то конечно же String[][] находится в явном преимуществе. Но, так ли он удобен и универсален в выборе? Опять таки же, ответ ещё и ещё раз говорит о том, что при проектировании той или иной программы, нужно тщательно продумывать где какой и какого вида нужен объект.

java.util.LinkedList

Ну и чтобы довести тест до конца я решил его закончить, определив количество выделяемой памяти для 10000 копий обеъкта TestClass в объекте LinkedList.

public class LinkedListTCFactory implements ObjectFactory {
    public Object makeObject() {
        LinkedList result = new LinkedList();
        TestClass tc;
        for (int i = 0; i < 10000; i++) {
            tc = new TestClass();
            tc.setfBt((byte)12);
            tc.setfByte(new Byte((byte)12));
            tc.setfChr('a');
            tc.setfCld(Calendar.getInstance());
            tc.setfDbl(123.45);
            tc.setfDouble(new Double(123.45));
            tc.setfDt(new Date());
            tc.setfFloat(new Float(123.45));
            tc.setfFlt(123.45f);
            tc.setfInt(123);
            tc.setfInteger(new Integer(123));
            tc.setfLng(1234567891234L);
            tc.setfLong(new Long(1234567891234L));
            tc.setfStr(new String("Field"));
            result.add(tc);
        }
        return result;
    }
}

Содержание объекта ничем так же не отличается от предыдущих примеров, за исключением того, что все они будут "складироваться" в LinkedList.

Factories.LinkedListTCFactory produced java.util.LinkedList which took 6959368 bytes

Протестировав VectorTCFactory и ArrayListTCFactory, тест с LinkedList можно было и не проводить. Уже и без того было понятно, что результаты теста зашкалят и отобразят самый высокий показатель среди всех. Тем не менее, очень хотелось посмотреть на сколько этот показатель будет высокий. Как и ожидалось и как видно по результату, 10 тысяч копий обекта заняли в оперативной памяти почти 7 мегабайт(!!!).

Ну и напоследок, как я и обещал, привожу общую таблицу проведенных тестов над объектами

Test Factory 64bit 32bit
BasicObjectFactory 16 8
ByteFactory 16 16
ByteThreeFactory 16 16
BooleanSixtyFourFactory 80 72
StringFactory 72 64
BooleanArrayFactory 199608 200016
ByteArrayPrimitiveFactory 10016 10016
VectorFactory 199640 120040
ArrayListFactory 199632 120040
LinkedListFactory 399640 320048
StringArrayFactory 520 408
StringArrayTCFactory 5239608 4120016
HashMapSimpleFactory 152 120
HashMapTCFactory 65272 65648
TestClassFactory 672 624
FieldsArrayFactory 1080 1080
MethodsArrayFactory 3424 3424
VectorTCFactory 6759368 6280040
ArrayListTCFactory 6759360 6280040
LinkedListTCFactory 6959368 6480048

Конечно же, можно было бы и продолжить дальнейшее исследование с различными вариантами объектов и их сочетаниями. Однако, этот результат мне показал все, что мне требовалось. Я думаю, что приведенные результаты, вам так же помогли определиться с тем, каким методом воспользоваться для определения того или иного объекта.
Так же, для тех, кому интересно продолжить тесты, могут скачать исходые коды. Данный архив является готовым проектом для NetBeans.
И не забывайте делиться ссылками на источник, если кто то решит разместить часть данной статьи или всю ее полностью.

Спасибо за внимание.