Вопрос по java, android – Является ли Dalvik еще более требовательным к памяти, чем HotSpot с точки зрения размеров объектов?

26

Мне было интересно, сколько памяти занимает объект на Android. Есть множество ресурсов (например,этот) связано с тем, что HotSpot JVM сообщает, что пустой объект занимает 8 байт и пустой массив 12 байтов и что все объекты выровнены по границе 8 байтов. Таким образом, объект без дополнительных полей должен занимать 8 байт, самый маленький объект с по крайней мере одним дополнительным полем & # x2013; 16 байтов, пустой массив & # x2013; 16 байтов, верно?

Я не нашел конкретной информации о Dalvik по этому вопросу и решил выяснить это путем тестирования. Выполнение теста былоsurprising results.

Несколько слов о методе расчета. Реализация Object.hashCode () в Android просто возвращает указатель на объект, приведенный к int. (казалось очевидным и общим, но [еще один сюрприз], как выяснилось, он НЕ работает на JSM HotSpot, например & # x2013; запустите MemTest с HotSpot и посмотрите). Итак, я использовал простоту hashCode () в Dalvik, чтобы вычислить размер объекта в Android, выделив два экземпляра тестируемого класса в строке, и количество выделенного пространства должно быть равно разности их hashCode (). значения (при условии, что Dalvik не имеет смысла размещать их по совершенно случайным адресам). Просто чтобы быть уверенным, что я выделяю всегда 4 объекта в строке для каждого тестового класса, что всегда дает одинаковое различие hashCode (). Поэтому я считаю, что сомнений в правильности метода мало.

Вот исходный код теста:

public class MemTest {
    public static void run() {
        Object o1 = new Object();
        Object o2 = new Object();
        Object o3 = new Object();
        Object o4 = new Object();

        EmptyObject eo1 = new EmptyObject();
        EmptyObject eo2 = new EmptyObject();
        EmptyObject eo3 = new EmptyObject();
        EmptyObject eo4 = new EmptyObject();

        ObjectWithBoolean ob1 = new ObjectWithBoolean();
        ObjectWithBoolean ob2 = new ObjectWithBoolean();
        ObjectWithBoolean ob3 = new ObjectWithBoolean();
        ObjectWithBoolean ob4 = new ObjectWithBoolean();

        ObjectWithBooleanAndInt obi1 = new ObjectWithBooleanAndInt();
        ObjectWithBooleanAndInt obi2 = new ObjectWithBooleanAndInt();
        ObjectWithBooleanAndInt obi3 = new ObjectWithBooleanAndInt();
        ObjectWithBooleanAndInt obi4 = new ObjectWithBooleanAndInt();

        ObjectWithLong ol1 = new ObjectWithLong();
        ObjectWithLong ol2 = new ObjectWithLong();
        ObjectWithLong ol3 = new ObjectWithLong();
        ObjectWithLong ol4 = new ObjectWithLong();

        ObjectWith4Ints o4i1 = new ObjectWith4Ints();
        ObjectWith4Ints o4i2 = new ObjectWith4Ints();
        ObjectWith4Ints o4i3 = new ObjectWith4Ints();
        ObjectWith4Ints o4i4 = new ObjectWith4Ints();

        ObjectWith4IntsAndByte o4ib1 = new ObjectWith4IntsAndByte();
        ObjectWith4IntsAndByte o4ib2 = new ObjectWith4IntsAndByte();
        ObjectWith4IntsAndByte o4ib3 = new ObjectWith4IntsAndByte();
        ObjectWith4IntsAndByte o4ib4 = new ObjectWith4IntsAndByte();

        ObjectWith5Ints o5i1 = new ObjectWith5Ints();
        ObjectWith5Ints o5i2 = new ObjectWith5Ints();
        ObjectWith5Ints o5i3 = new ObjectWith5Ints();
        ObjectWith5Ints o5i4 = new ObjectWith5Ints();

        ObjectWithArrayRef oar1 = new ObjectWithArrayRef();
        ObjectWithArrayRef oar2 = new ObjectWithArrayRef();
        ObjectWithArrayRef oar3 = new ObjectWithArrayRef();
        ObjectWithArrayRef oar4 = new ObjectWithArrayRef();

        byte[] a0b1 = new byte[0];
        byte[] a0b2 = new byte[0];
        byte[] a0b3 = new byte[0];
        byte[] a0b4 = new byte[0];

        byte[] a1b1 = new byte[1];
        byte[] a1b2 = new byte[1];
        byte[] a1b3 = new byte[1];
        byte[] a1b4 = new byte[1];

        byte[] a5b1 = new byte[5];
        byte[] a5b2 = new byte[5];
        byte[] a5b3 = new byte[5];
        byte[] a5b4 = new byte[5];

        byte[] a9b1 = new byte[9];
        byte[] a9b2 = new byte[9];
        byte[] a9b3 = new byte[9];
        byte[] a9b4 = new byte[9];

        byte[] a12b1 = new byte[12];
        byte[] a12b2 = new byte[12];
        byte[] a12b3 = new byte[12];
        byte[] a12b4 = new byte[12];

        byte[] a13b1 = new byte[13];
        byte[] a13b2 = new byte[13];
        byte[] a13b3 = new byte[13];
        byte[] a13b4 = new byte[13];

        print("java.lang.Object", o1, o2, o3, o4);
        print("Empty object", eo1, eo2, eo3, eo4);
        print("Object with boolean", ob1, ob2, ob3, ob4);
        print("Object with boolean and int", obi1, obi2, obi3, obi4);
        print("Object with long", ol1, ol2, ol3, ol4);
        print("Object with 4 ints", o4i1, o4i2, o4i3, o4i4);
        print("Object with 4 ints and byte", o4ib1, o4ib2, o4ib3, o4ib4);
        print("Object with 5 ints", o5i1, o5i2, o5i3, o5i4);

        print("Object with array ref", new Object[]{oar1, oar2, oar3, oar4});

        print("new byte[0]", a0b1, a0b2, a0b3, a0b4);
        print("new byte[1]", a1b1, a1b2, a1b3, a1b4);
        print("new byte[5]", a5b1, a5b2, a5b3, a5b4);
        print("new byte[9]", a9b1, a9b2, a9b3, a9b4);
        print("new byte[12]", a12b1, a12b2, a12b3, a12b4);
        print("new byte[13]", a13b1, a13b2, a13b3, a13b4);
    }

    static void print(String title, Object... objects) {
        StringBuilder buf = new StringBuilder(title).append(":");
        int prevHash = objects[0].hashCode();
        int prevDiff = -1;
        for (int i = 1; i < objects.length; i++) {
            int hash = objects[i].hashCode();
            int diff = Math.abs(hash - prevHash);
            if (prevDiff == -1 || prevDiff != diff) {
                buf.append(' ').append(diff);
            }
            prevDiff = diff;
            prevHash = hash;
        }
        System.out.println(buf.toString());
    }

    /******** Test classes ******/

    public static class EmptyObject {
    }

    public static class ObjectWith4Ints {
        int i1;
        int i2;
        int i3;
        int i4;
    }

    public static class ObjectWith4IntsAndByte {
        int i1;
        int i2;
        int i3;
        int i4;
        byte b;
    }

    public static class ObjectWith5Ints {
        int i1;
        int i2;
        int i3;
        int i4;
        int i5;
    }

    public static class ObjectWithArrayRef {
        byte[] b;
    }

    public static class ObjectWithBoolean {
        boolean b;
    }

    public static class ObjectWithBooleanAndInt {
        boolean b;
        int i;
    }

    public static class ObjectWithLong {
        long l;
    }
}

and here are the results:

java.lang.Object: 16
Empty object: 16
Object with boolean: 16
Object with boolean and int: 24
Object with long: 24
Object with 4 ints: 32
Object with 4 ints and byte: 32
Object with 5 ints: 32
Object with array ref: 16
new byte[0]: 24
new byte[1]: 24
new byte[5]: 32
new byte[9]: 32
new byte[12]: 32
new byte[13]: 40

Подводя итоги:

8 byte boundary alignment is the same as on HotSpot, and that's the only thing that is the same.

minimum of 16 bytes for a plain Object (vs 8 on HotSpot)

apparently an empty object itself occupies 12 bytes (vs 8 on HotSpot) and there is room for 4 extra bytes until object size 'jumps' from 16 bytes to the next boundary of 24 bytes.

minimum of 24 bytes for an empty array (vs 12 on HotSpot)

similarly an array itself occupies 20 bytes (vs 12 on HotSpot) and there is room for 4 extra bytes of array data until object size 'jumps' from 24 bytes to the next boundary of 32 bytes.

ADDITION: (в ответ на предложение Луи) Другой стресс-тест показывает, что даже при выделении миллиона экземпляров Object расстояние между любыми двумя НИКОГДА не будет меньше 16 байт. Это доказательство того, что потенциальные 8-байтовые дыры между объектами, безусловно, являются мертвым пространством для дальнейших распределений, в противном случае к тому времени, когда около половины памяти было выделено для объектов, dalvik определенно должен был поместить некоторые из них в «дыры». и стресс-тест вернул бы 8, а не 16.

public static void run2() {
    int count = 1024 * 1024;
    Object[] arr = new Object[count];
    for (int i = 0; i < count; i++) {
        arr[i] = new Object();
    }
    int[] hashes = new int[count];
    for (int i = 0; i < count; i++) {
        hashes[i] = arr[i].hashCode();
    }
    Arrays.sort(hashes);

    int minDist = Integer.MAX_VALUE;
    for (int i = 1; i < count; i++) {
        int dist = Math.abs(hashes[i] - hashes[i - 1]);
        if (dist < minDist) {
            minDist = dist;
        }
    }
    System.out.println("Allocated "+ count + " Objects, minimum distance is "+ minDist);
}

Правильно ли я понимаю, что ДальвикObject takes up to 8 more bytes а такжеarray 8-12 more bytes по сравнению с HotSpot?

Что ты имеешь в виду под "неподдерживаемым"? Gena Batsyan
Я полагаю, что даже если между объектами есть «дыры» (возможно, 4, 8 байтов), это определенно мертвое пространство и вполне может считаться частью размера объекта, потому что в конечном итоге важно только то, как много памяти эффективно съедается (не используется для выделения другого объекта) путем выделения N объектов. Их выравнивание и т. Д. Не имеет никакого отношения к эффективному использованию памяти на объек Gena Batsyan
«Предполагая, что для Далвика не имеет смысла распределять их по совершенно случайным адресам» Случайно, возможно, нет, но последовательно? Там могут быть ограничения выравнивания или много других вещей, происходящих. Интересно, есть ли в Google подробности о выделении памяти Android о таких веща Louis Wasserman
Здесь есть пара неподдерживаемых предположений ... Louis Wasserman

Ваш Ответ

2   ответа
12

(Да, это старый вопрос, но результаты были довольно интересные, поэтому я немного ткнул в него.

TheObject.clone(етод @ должен сделать полную побитовую копию объекта. Для этого нужно знать, насколько большой объект. Если вы посмотрите наdvmCloneObject(, вы видите, что он использует один метод для массивов и другой метод для объектов.

Для массивов это называетсяdvmArrayObjectSize(, который умножает длину массива на ширину элемента (1, 2, 4 или 8, а затем добавляет смещение данных массива от начала объекта. Каждый объект имеет 8-байтовый заголовок; массивы имеют ширину 4 байта и включают дополнительные 4 байта заполнения, чтобы обеспечить правильное выравнивание 64-битных значений. Так что для 5-элементного массиваshort было бы 16 + 5 * 2.

Для обычных объектов он просто используетobjectSize поле в объекте класса. Это устанавливается довольно сложной функцией под названиемcomputeFieldOffsets(. Эта функция гарантирует, что все ссылки на объекты идут первыми (поэтому GC может пропускать меньше при сканировании, а затем следует со всеми 64-битными полями. Чтобы обеспечить правильное выравнивание 64-битных полей, оно может переместить одно из 32-битных примитивных полей вверх, чтобы дополнить их. (Если нет подходящего 32-битного поля, вы просто получаете 4 байта заполнения.

Я должен добавить: все поля 32-битные, кромеlong а такжеdouble, которые 64-битные. Ссылки на объекты являются 32-битными.

Так что сложно точно сказать, насколько большим будет объект, не являющийся массивом, но в общем случае вы берете 8-байтовый заголовок объекта, суммируете ширину дополнительных полей и округляете до следующего кратного 8 байтам - последний, потому что все объекты должны быть выровнены по 64-битному ти

Так что это теория. Чтобы увидеть это на практике, я добавил это вdvmCloneObject(:

ALOGD("class=%s size=%d", clazz->descriptor, clazz->objectSize;

и увидел вывод logcat как:

D dalvikvm: class=Ljava/util/Locale; size=24
D dalvikvm: class=Ljava/util/Date; size=16

Locale имеет 4 справочных поля, у Date есть одноlong поле, поэтому эти значения соответствуют ожиданиям.

Идеально, именно столько места потребуется. Тем не менее, объект выделяется сmspace_calloc(, который добавляет еще 4 или (иногда 8 байтов служебной информации. Так что Фактической пространство, требуемое для значений выше, будет 32 и 24, что соответствует вашим экспериментальным результатам.

Мне интересно, почему объект без полей а также объект с одним целым полем занимают одинаковое количество места (оба по 16 байт). Лиmspace_calloc имеют служебные данные 4 или 8 байтов? Cyker
Начало распределения dlmalloc (которое возвращает malloc ()) должно быть выровнено в 8 байтов. 4-байтовые издержки (размер блока) идут непосредственно перед этим. Код dlmalloc включает в себя несколько хороших ASCII-иллюстраций - начните с строки 2047 в Android.googlesource.com / Платформа / бионической / + / ДБ-mr2-релиз / Libc / .... Чтобы добавить дополнительный вкус, есть также «Минимальный выделенный размер: 4-байтовые ptrs: 16 байт (включая служебные данные)». fadden
Документы dlmalloc говорят: «Минимальные издержки на выделенный блок: 4 или 8 байтов (если размер 4 байта)». Я полагаю, что это зависит от того, как выполняется распределение (локальное или mmap), что может зависеть от размера запроса на выделение. Все объекты выровнены по 8 байтов, поэтому 12-байтовый объект и 16-байтовый объект будут занимать 16 байтов. fadden
Объект без полей имеет размер<dlmalloc_overhead> + sizeof(struct Object) = <dlmalloc_overhead + 8>. Объект с одним полем типа int имеет размер<dlmalloc_overhead + sizeof(struct Object) + sizeof(int) = <dlmalloc_overhead> + 12. Поскольку конечные результаты составляют 16 байтов, я думаю,<dlmalloc_overhead> равно 4. Но это означает, что во втором случае метаданные бухгалтерии dlmalloc и указатель объектане могу оба должны быть выровнены по 8 байтов (если данные бухгалтерии dlmalloc помещены перед выделенным объектом. Это правда?) Возможно ли, чтобы метаданные бухгалтерии dlmalloc не были выровнены по 8 байтам? Cyker
3

но я могу предложить пару мест, где вы можете найти дополнительную информацию.

Вы можете взглянуть на структуры DataObject и ArrayObject в dalvik / vm / oo / Object.h. Исходя из этого, кажется, что пустой объект должен занимать только 8 байтов, в то время как пустой массив должен занимать 12 байтов. Похоже, это не соответствует вашим результатам, хотя я не уверен, почему.

Вы также можете посмотреть на использование поля objectSize в структуре ClassObject для большей наглядности. Быстрый поиск использования этого поля показывает, что метод dvmAllocObject в dalvik / vm / alloc / Alloc.cpp, похоже, отвечает за выделение памяти для новых объектов.

Dalvik также использует dlmalloc за кулисами и выполняет свои собственные распределени Jesse Wilson

Похожие вопросы