Как узнать размер объекта (коллекции) в памяти?

217
13 марта 2018, 02:44

Мне нужно узнать, сколько байт в оперативной памяти занимает определенная коллекция в моем приложении (ArrayList). Нужно это для того, чтобы представлять, сколько объектов я могу в нее добавить, не получив OutOfMemory.

Как посчитать вручную я представляю, но хотел бы узнать, есть ли способ посчитать программно.

Конкретно свою проблему я решил, но вопрос остается открытым, ибо мое решение не связано с определением объема памяти, занимаемого объекта.
Итак, я просто решил на практике проверить, сколько же объектов без проблем удержится в памяти на разных моделях телефонов.

java.lang.instrument в Android (Dalvic/ART) не существует.

Answer 1
  1. Самый простой способ — сделать Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() до создания достаточно большой тестовой коллекции и после. Повторить несколько раз, чтобы исключить случайности.
  2. Более сложный — это использовать java.lang.instrumentation package и т.п. для определения размера среднего объекта в коллекции и по нему прикидывать размер коллекции.

    Сам по себе ArrayList занимает всего 4(8) байт на элемент (в зависимости от размера указателя, причем даже в 64 битной системе указатели могут быть 4 байтные), то есть основные затраты памяти будут именно на объекты, содержащиеся в ArrayList. Но определение размера объекта в Java вещь сложная, так как не понятно что считать. Если считать только размер самих полей-ссылок — размер объектов будет совсем небольшим, если считать в том числе и все объекты, на которые данные объекты ссылаются, то много объектов могут ссылаться на один общий объект и он посчитается несколько раз (более того все объекты в системе могут ссылаться друг на друга и размер любого объекта можно посчитать равным всем объектам системы). Так что первый способ предпочтительнее.

Answer 2

В процессе исследования своего вопроса набросал класс, который анализирует объект, и считает объем памяти, который тот занимает. Считает не точно. Честно сказать, не знаю даже, с какой погрешностью:

public class MemoryUtils {
    private static Map<Class, Integer> primitiveSizes = new HashMap<>();
    static {
        primitiveSizes.put(byte.class, 1);
        primitiveSizes.put(short.class, 2);
        primitiveSizes.put(int.class, 4);
        primitiveSizes.put(long.class, 8);
        primitiveSizes.put(float.class, 4);
        primitiveSizes.put(double.class, 8);
        primitiveSizes.put(char.class, 2);
        primitiveSizes.put(boolean.class, 1);
    }
    private static Set<Class> boxed = new HashSet<>();
    static {
        boxed.add(Byte.class);
        boxed.add(Short.class);
        boxed.add(Integer.class);
        boxed.add(Long.class);
        boxed.add(Float.class);
        boxed.add(Double.class);
        boxed.add(Character.class);
        boxed.add(Boolean.class);
    }
    static final int REFERENCE_SIZE = 4;
    static long calls = 0;
    public static long sizeOf(Object object, List<Object> calculated, Class clazz) {
        try {
            calls++;
            if (calls % 1000 == 0) {
                Log.d("MemoryUtils", "call # " + calls);
            }
            if (calculated == null) {
                calculated = new ArrayList<>();
            }
            if (object == null) {
                return 0;
            }
            long result = 0;
            if (clazz == null) {
                clazz = object.getClass();
            }
            if(clazz == String.class) {
                return ((String)object).length() * 2 + 38;
            }
            if (object.getClass() == Object.class) {
                return REFERENCE_SIZE * 2;
            }
            if (clazz.isPrimitive()) {
                int size = primitiveSizes.get(clazz);
                result += size;
            } else if (clazz.isArray()) {
                int length = Array.getLength(object);
                for (int i = 0; i < length; i++) {
                    Object element = Array.get(object, i);
                    if (!calculated.contains(element) && element != null) {
                        Class typeToPass = element.getClass();
                        if (boxed.contains(typeToPass)) {
                            typeToPass = object.getClass().getComponentType();
                        }
                        result += sizeOf(element, calculated, typeToPass);
                    }
                }
            } else {
                result += REFERENCE_SIZE;
                calculated.add(object);
                for (Field f : clazz.getDeclaredFields()) {
                    if (java.lang.reflect.Modifier.isStatic(f.getModifiers())) {
                        continue;
                    }
                    f.setAccessible(true);
                    try {
                        Object value = f.get(object);
                        if (!calculated.contains(value) && value != null) {
                            Class typeToPass = value.getClass();
                            if (boxed.contains(typeToPass)) {
                                typeToPass = f.getType();
                            }
                            result += sizeOf(value, calculated, typeToPass);
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            Log.d("MemoryUtils", "class = " + clazz.getName() + " value = " + object + " size = " + result + " isPrimitive = " + clazz.isPrimitive());
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }
}

Использовать так:

MemoryUtils.sizeOf(someObject, null, null);
Answer 3

java -jar jol-cli-0.5-full.jar internals 'java.util.ArrayList'

Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
java.util.ArrayList object internals:
 OFFSET  SIZE     TYPE DESCRIPTION               VALUE
      0     4          (object header)           01 00 00 00 
      4     4          (object header)           00 00 00 00
      8     4          (object header)           1e 2f 00 f8 
     12     4      int AbstractList.modCount     0
     16     4      int ArrayList.size            0
     20     4 Object[] ArrayList.elementData     []

Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

Update:

Ссылка на Exploring Java's Hidden Costs

Ссылка на jol-cli-0.5-full.jar

Answer 4

Можно попробовать так. На вход засовываете всё, что угодно, на выходе получаем длину byteArray-я :

public static int getMemoryLength(Object object) throws java.io.IOException
{
    ByteArrayOutputStream byteObject = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteObject);
    objectOutputStream.writeObject(object);
    objectOutputStream.flush();
    objectOutputStream.close();
    byteObject.close();
    return byteObject.toByteArray().length;
}
Answer 5

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

В большинстве своем хранение информации сводится к хранению символов и чисел.

Можно рассчитать, сколько символов или чисел поместиться в памяти, но не объектов (примитивы можно).

Пример:

Сколько места в памяти займет объект String?

Ровно столько, сколько в него будет положено символов + служебная информация. Мы можем рассчитать сколько объектов поместиться на 100Mb. Пусть объектами будут отдельные слова и условно у нас помещается 1000 строк по 1 слову.

В runtime мы начинаем получать не 1 слово, а 3 и со всеми расчетами ловим OutOfMemory.

Альтернативный путь, ограничение размера объекта изначально как это сделано с примитивами и их обертками. Как бы мы ни старались, положить в int значение больше 2*32 не получиться. Если свои объекты ограничить так же, то вы сможете рассчитать сколько объектов сможете хранить в памяти.

В принципе если бы вы знали сколько объектов можете хранить в памяти, List вам был бы не нужен и можно было использовать фиксированный массив

READ ALSO
Java. Проблемы с получением текущей даты в ubuntu 17.10

Java. Проблемы с получением текущей даты в ubuntu 17.10

Добрый день! Написал тут программу, которая должна считать кол-во дней до какой-то датыСтолкнулся с тем, что не совсем корректно работают...

135
wait не получает notify на случайной итерации

wait не получает notify на случайной итерации

Задача: Сделать 3 потока, которые будут выполнятся друг за другом, то есть 2-ой поток идет за 1-ым, а 3-ий за вторым, 1-ый за 3-имЗадача должна быть...

141
Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1

Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1

андроид студио в интеллии, выскакивает такая ошибка:

151
Java Instrumentation

Java Instrumentation

Я создал jar файл для java агента для того чтобы использовать его в своем приложении , java агент подсчитывает размер объекта с помощью класса...

133