Подтвердить что ты не робот

Далвик еще больше голоден, чем HotSpot с точки зрения размеров объектов?

Мне интересно, сколько памяти занимает объект на Android. Существует множество ресурсов (например, this), связанных с JVM HotSpot, сообщающих, что пустой объект занимает 8 байтов и пустой массив 12 байтов и что все объекты выровнены с 8-байтовой границей. Таким образом, объект без дополнительных полей должен принимать 8 байтов, наименьший объект с по крайней мере одним дополнительным полем - 16 байтов, пустой массив - 16 байтов, правый?

Я не нашел никакой конкретной информации о Dalvik по этому вопросу и решил разобраться в этом путем тестирования. Запуск теста показал неожиданные результаты.

Несколько слов о методе вычисления. Android-реализация Object.hashCode() просто возвращает указатель на объект, выделенный для int. (казалось очевидным и общим, но [еще один сюрприз], как выяснилось, он НЕ на HotSpot JVM, например, запускает 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;
    }
}

и вот результаты:

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-байтовое выравнивание границы такое же, как и на HotSpot, и это единственное, что то же самое.

  • минимум 16 байтов для простого объекта (vs 8 на HotSpot)

  • по-видимому, пустой объект сам занимает 12 байт (vs 8 на HotSpot), и есть место для 4 дополнительных байтов, пока размер объекта не "перескочит" с 16 байт до следующей границы из 24 байтов.

  • минимум 24 байта для пустого массива (vs 12 на HotSpot)

  • аналогично, массив сам занимает 20 байт (vs 12 на HotSpot), и есть место для 4 дополнительных байтов данных массива, пока размер объекта не перескакивает с 24 байтов на следующую границу из 32 байтов.

ДОБАВЛЕНИЕ: (в ответ на предложение Луи) Другой стресс-тест показывает, что даже выделение миллиона экземпляров объектов расстояние между любыми двумя никогда не меньше 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);
}

Я правильно понимаю, что Dalvik Объект занимает до 8 байт и массив еще 8-12 байтов по сравнению с HotSpot?

4b9b3361

Ответ 1

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

Метод Object.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

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

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

Ответ 2

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

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

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