Czy Dalvik jest jeszcze bardziej głodny pamięci niż HotSpot pod względem wielkości obiektów?

Zastanawiałem się, ile pamięci zajmuje obiekt na Androidzie. Istnieje wiele zasobów (npto) związane z JVM HotSpot informujące, że pusty obiekt ma 8 bajtów i pustą tablicę 12 bajtów oraz że wszystkie obiekty są wyrównane do granicy 8 bajtów. Tak więc obiekt bez dodatkowych pól powinien mieć 8 bajtów, najmniejszy obiekt z co najmniej jednym dodatkowym polem - 16 bajtów, pustą tablicą - 16 bajtów, prawda?

Nie znalazłem żadnych konkretnych informacji na temat Dalvika w tej sprawie i postanowiłem to sprawdzić, testując. Przeprowadzono testzaskakujące wyniki.

Kilka słów o metodzie obliczeń. Implementacja Object.hashCode () w Androidzie po prostu zwraca wskaźnik do obiektu rzutowanego na int. (wydawało się oczywiste i ogólne, ale jak się okazało, [inna niespodzianka], nie działa na przykład na HotSpot JVM - uruchom MemTest z HotSpot i zobacz). Użyłem prostoty hashCode () na Dalviku do obliczenia rozmiaru obiektu na Androidzie, przydzielając dwa wystąpienia testowanej klasy w rzędzie, a ilość przydzielonego miejsca powinna być równa różnicy ich kodu hashCode () wartości (zakładając, że Dalvik nie ma sensu przydzielać tych z całkowicie losowych adresów). Aby mieć pewność, że przydzieliłem zawsze 4 obiekty w rzędzie na klasę testową, które zawsze dostarczały tę samą różnicę w hashCode (). Sądzę więc, że nie ma wątpliwości co do poprawności metody.

Oto kod źródłowy testu:

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;
    }
}

a oto wyniki:

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

Podsumowując wyniki:

8-bajtowe wyrównanie granicy jest takie samo jak w HotSpot i to jedyna rzecz, która jest taka sama.

minimum 16 bajtów dla zwykłego obiektu (vs 8 na HotSpot)

najwyraźniej sam pusty obiekt zajmuje 12 bajtów (w porównaniu z 8 na HotSpot) i jest miejsce na 4 dodatkowe bajty, aż rozmiar obiektu „przeskoczy” z 16 bajtów do następnej granicy 24 bajtów.

minimum 24 bajty dla pustej tablicy (vs 12 na HotSpot)

podobnie sama tablica zajmuje 20 bajtów (w porównaniu z 12 na HotSpot) i jest miejsce na 4 dodatkowe bajty danych tablicowych, aż rozmiar obiektu „przeskoczy” z 24 bajtów do następnej granicy 32 bajtów.

DODANIE: (w odpowiedzi na sugestię Louisa) Kolejny test warunków skrajnych pokazuje, że nawet przydzielenie miliona instancji obiektów odległość między dowolnymi dwoma jest NIGDY mniejsza niż 16 bajtów. Jest to dowód na to, że potencjalne 8-bajtowe dziury między obiektami są zdecydowanie martwą przestrzenią dla dalszych alokacji, w przeciwnym razie do czasu, gdy około połowa pamięci zostanie przydzielona do Objects dalvik powinien zdecydowanie umieścić niektóre z nich również w „dziurach”, a test warunków skrajnych zwróci 8, a nie 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);
}

Czy widzę to dobrze, że DalvikObiekt zajmuje do 8 dodatkowych bajtów itablica 8-12 kolejnych bajtów w porównaniu do HotSpot?

questionAnswers(2)

yourAnswerToTheQuestion