В Java, как лучше всего определить размер объекта? - PullRequest
568 голосов
/ 09 сентября 2008

Например, допустим, у меня есть приложение, которое может читать в CSV-файл с кучами строк данных. Я даю пользователю сводную информацию о количестве строк на основе типов данных, но хочу убедиться, что я не читаю слишком много строк данных и вызываю OutOfMemoryError s. Каждая строка переводится в объект. Есть ли простой способ узнать размер этого объекта программно? Есть ли ссылка, которая определяет, насколько велики примитивные типы и ссылки на объекты для VM?

Сейчас у меня есть код, который читает до 10000 * 32 000 строк , но я также хотел бы, чтобы код читал как можно больше строк, пока я не использовал 32 МБ памяти. Может быть, это другой вопрос, но я все еще хотел бы знать.

Ответы [ 24 ]

6 голосов
/ 14 мая 2012

Класс java.lang.instrument.Instrumentation предоставляет хороший способ получить размер Java-объекта, но требует, чтобы вы определили premain и запустили вашу программу с помощью Java-агента. Это очень скучно, когда вам не нужен агент, а затем вы должны предоставить фиктивный агент Jar для вашего приложения.

Итак, я получил альтернативное решение, используя класс Unsafe из sun.misc. Таким образом, учитывая выравнивание кучи объектов в соответствии с архитектурой процессора и вычисляя максимальное смещение поля, вы можете измерить размер Java-объекта. В приведенном ниже примере я использую вспомогательный класс UtilUnsafe, чтобы получить ссылку на объект sun.misc.Unsafe.

private static final int NR_BITS = Integer.valueOf(System.getProperty("sun.arch.data.model"));
private static final int BYTE = 8;
private static final int WORD = NR_BITS/BYTE;
private static final int MIN_SIZE = 16; 

public static int sizeOf(Class src){
    //
    // Get the instance fields of src class
    // 
    List<Field> instanceFields = new LinkedList<Field>();
    do{
        if(src == Object.class) return MIN_SIZE;
        for (Field f : src.getDeclaredFields()) {
            if((f.getModifiers() & Modifier.STATIC) == 0){
                instanceFields.add(f);
            }
        }
        src = src.getSuperclass();
    }while(instanceFields.isEmpty());
    //
    // Get the field with the maximum offset
    //  
    long maxOffset = 0;
    for (Field f : instanceFields) {
        long offset = UtilUnsafe.UNSAFE.objectFieldOffset(f);
        if(offset > maxOffset) maxOffset = offset; 
    }
    return  (((int)maxOffset/WORD) + 1)*WORD; 
}
class UtilUnsafe {
    public static final sun.misc.Unsafe UNSAFE;

    static {
        Object theUnsafe = null;
        Exception exception = null;
        try {
            Class<?> uc = Class.forName("sun.misc.Unsafe");
            Field f = uc.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            theUnsafe = f.get(uc);
        } catch (Exception e) { exception = e; }
        UNSAFE = (sun.misc.Unsafe) theUnsafe;
        if (UNSAFE == null) throw new Error("Could not obtain access to sun.misc.Unsafe", exception);
    }
    private UtilUnsafe() { }
}
6 голосов
/ 06 сентября 2013

Существует также инструмент Memory Measurer (ранее Google Code , теперь GitHub ), который прост и публикуется под коммерческим Лицензия Apache 2.0 , как обсуждалось в подобном вопросе .

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

3 голосов
/ 03 апреля 2015

Без необходимости возиться с инструментарием и т. Д., И если вам не нужно знать точный размер объекта в байтах, вы можете использовать следующий подход:

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

do your job here

System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

Таким образом, вы читаете использованную память до и после, и, вызывая GC непосредственно перед получением использованной памяти, вы понижаете «шум» почти до 0.

Для более надежного результата вы можете выполнить свою работу n раз, а затем разделить использованную память на n, получив, сколько памяти занимает один запуск. Более того, вы можете запустить все это больше раз и получить среднее значение.

3 голосов
/ 26 июня 2014

Вот утилита, которую я сделал, используя некоторые из связанных примеров для обработки 32-битных, 64-битных и 64-битных сжатых ООП. Используется sun.misc.Unsafe.

Используется Unsafe.addressSize() для получения размера собственного указателя и Unsafe.arrayIndexScale( Object[].class ) для размера ссылки на Java.

Используется смещение поля известного класса для определения базового размера объекта.

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Stack;
import sun.misc.Unsafe;

/** Usage: 
 * MemoryUtil.sizeOf( object )
 * MemoryUtil.deepSizeOf( object )
 * MemoryUtil.ADDRESS_MODE
 */
public class MemoryUtil
{
    private MemoryUtil()
    {
    }

    public static enum AddressMode
    {
        /** Unknown address mode. Size calculations may be unreliable. */
        UNKNOWN,
        /** 32-bit address mode using 32-bit references. */
        MEM_32BIT,
        /** 64-bit address mode using 64-bit references. */
        MEM_64BIT,
        /** 64-bit address mode using 32-bit compressed references. */
        MEM_64BIT_COMPRESSED_OOPS
    }

    /** The detected runtime address mode. */
    public static final AddressMode ADDRESS_MODE;

    private static final Unsafe UNSAFE;

    private static final long ADDRESS_SIZE; // The size in bytes of a native pointer: 4 for 32 bit, 8 for 64 bit
    private static final long REFERENCE_SIZE; // The size of a Java reference: 4 for 32 bit, 4 for 64 bit compressed oops, 8 for 64 bit
    private static final long OBJECT_BASE_SIZE; // The minimum size of an Object: 8 for 32 bit, 12 for 64 bit compressed oops, 16 for 64 bit
    private static final long OBJECT_ALIGNMENT = 8;

    /** Use the offset of a known field to determine the minimum size of an object. */
    private static final Object HELPER_OBJECT = new Object() { byte b; };


    static
    {
        try
        {
            // Use reflection to get a reference to the 'Unsafe' object.
            Field f = Unsafe.class.getDeclaredField( "theUnsafe" );
            f.setAccessible( true );
            UNSAFE = (Unsafe) f.get( null );

            OBJECT_BASE_SIZE = UNSAFE.objectFieldOffset( HELPER_OBJECT.getClass().getDeclaredField( "b" ) );

            ADDRESS_SIZE = UNSAFE.addressSize();
            REFERENCE_SIZE = UNSAFE.arrayIndexScale( Object[].class );

            if( ADDRESS_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_32BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 8 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT;
            }
            else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 4 )
            {
                ADDRESS_MODE = AddressMode.MEM_64BIT_COMPRESSED_OOPS;
            }
            else
            {
                ADDRESS_MODE = AddressMode.UNKNOWN;
            }
        }
        catch( Exception e )
        {
            throw new Error( e );
        }
    }


    /** Return the size of the object excluding any referenced objects. */
    public static long shallowSizeOf( final Object object )
    {
        Class<?> objectClass = object.getClass();
        if( objectClass.isArray() )
        {
            // Array size is base offset + length * element size
            long size = UNSAFE.arrayBaseOffset( objectClass )
                    + UNSAFE.arrayIndexScale( objectClass ) * Array.getLength( object );
            return padSize( size );
        }
        else
        {
            // Object size is the largest field offset padded out to 8 bytes
            long size = OBJECT_BASE_SIZE;
            do
            {
                for( Field field : objectClass.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0 )
                    {
                        long offset = UNSAFE.objectFieldOffset( field );
                        if( offset >= size )
                        {
                            size = offset + 1; // Field size is between 1 and PAD_SIZE bytes. Padding will round up to padding size.
                        }
                    }
                }
                objectClass = objectClass.getSuperclass();
            }
            while( objectClass != null );

            return padSize( size );
        }
    }


    private static final long padSize( final long size )
    {
        return (size + (OBJECT_ALIGNMENT - 1)) & ~(OBJECT_ALIGNMENT - 1);
    }


    /** Return the size of the object including any referenced objects. */
    public static long deepSizeOf( final Object object )
    {
        IdentityHashMap<Object,Object> visited = new IdentityHashMap<Object,Object>();
        Stack<Object> stack = new Stack<Object>();
        if( object != null ) stack.push( object );

        long size = 0;
        while( !stack.isEmpty() )
        {
            size += internalSizeOf( stack.pop(), stack, visited );
        }
        return size;
    }


    private static long internalSizeOf( final Object object, final Stack<Object> stack, final IdentityHashMap<Object,Object> visited )
    {
        // Scan for object references and add to stack
        Class<?> c = object.getClass();
        if( c.isArray() && !c.getComponentType().isPrimitive() )
        {
            // Add unseen array elements to stack
            for( int i = Array.getLength( object ) - 1; i >= 0; i-- )
            {
                Object val = Array.get( object, i );
                if( val != null && visited.put( val, val ) == null )
                {
                    stack.add( val );
                }
            }
        }
        else
        {
            // Add unseen object references to the stack
            for( ; c != null; c = c.getSuperclass() )
            {
                for( Field field : c.getDeclaredFields() )
                {
                    if( (field.getModifiers() & Modifier.STATIC) == 0 
                            && !field.getType().isPrimitive() )
                    {
                        field.setAccessible( true );
                        try
                        {
                            Object val = field.get( object );
                            if( val != null && visited.put( val, val ) == null )
                            {
                                stack.add( val );
                            }
                        }
                        catch( IllegalArgumentException e )
                        {
                            throw new RuntimeException( e );
                        }
                        catch( IllegalAccessException e )
                        {
                            throw new RuntimeException( e );
                        }
                    }
                }
            }
        }

        return shallowSizeOf( object );
    }
}
2 голосов
/ 16 ноября 2013

Один раз я написал быстрый тест, чтобы оценить на лету:

public class Test1 {

    // non-static nested
    class Nested { }

    // static nested
    static class StaticNested { }

    static long getFreeMemory () {
        // waits for free memory measurement to stabilize
        long init = Runtime.getRuntime().freeMemory(), init2;
        int count = 0;
        do {
            System.out.println("waiting..." + init);
            System.gc();
            try { Thread.sleep(250); } catch (Exception x) { }
            init2 = init;
            init = Runtime.getRuntime().freeMemory();
            if (init == init2) ++ count; else count = 0;
        } while (count < 5);
        System.out.println("ok..." + init);
        return init;
    }

    Test1 () throws InterruptedException {

        Object[] s = new Object[10000];
        Object[] n = new Object[10000];
        Object[] t = new Object[10000];

        long init = getFreeMemory();

        //for (int j = 0; j < 10000; ++ j)
        //    s[j] = new Separate();

        long afters = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            n[j] = new Nested();

        long aftersn = getFreeMemory();

        for (int j = 0; j < 10000; ++ j)
            t[j] = new StaticNested();

        long aftersnt = getFreeMemory();

        System.out.println("separate:      " + -(afters - init) + " each=" + -(afters - init) / 10000);
        System.out.println("nested:        " + -(aftersn - afters) + " each=" + -(aftersn - afters) / 10000);
        System.out.println("static nested: " + -(aftersnt - aftersn) + " each=" + -(aftersnt - aftersn) / 10000);

    }

    public static void main (String[] args) throws InterruptedException {
        new Test1();
    }

}

Общая концепция заключается в выделении объектов и измерении изменений в свободном пространстве кучи. Ключ getFreeMemory(), который запрашивает выполнение GC и ожидает стабилизации сообщенного размера свободной кучи . Выходные данные выше:

nested:        160000 each=16
static nested: 160000 each=16

Что мы и ожидаем, учитывая поведение выравнивания и возможные издержки заголовка блока кучи.

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

2 голосов
/ 23 марта 2018

Просто используйте Java Visual VM.

В нем есть все необходимое для профилирования и устранения проблем с памятью.

Он также имеет консоль OQL (Object Query Language), которая позволяет вам делать много полезных вещей, одним из которых является sizeof(o)

2 голосов
/ 09 сентября 2008

Нет вызова метода, если это то, что вы просите. С небольшим исследованием, я полагаю, вы могли бы написать свой собственный. Конкретный экземпляр имеет фиксированный размер, полученный из числа ссылок и примитивных значений плюс данные учета экземпляра. Вы бы просто шли по графу объектов. Чем менее разнообразны типы строк, тем легче.

Если это слишком медленно или просто больше проблем, чем стоит, всегда есть старое доброе правило подсчета строк.

1 голос
/ 16 октября 2014

Мой ответ основан на коде, предоставленном Ником. Этот код измеряет общее количество байтов, которые заняты сериализованным объектом. Таким образом, это фактически измеряет материал сериализации + объем памяти простого объекта (просто сериализуйте, например, int, и вы увидите, что общее количество сериализованных байтов не равно 4). Поэтому, если вы хотите получить необработанный номер байта, используемый именно для вашего объекта - вам нужно немного изменить этот код. Вот так:

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class ObjectSizeCalculator {
    private Object getFirstObjectReference(Object o) {
        String objectType = o.getClass().getTypeName();

        if (objectType.substring(objectType.length()-2).equals("[]")) {
            try {
                if (objectType.equals("java.lang.Object[]"))
                    return ((Object[])o)[0];
                else if (objectType.equals("int[]"))
                    return ((int[])o)[0];
                else
                    throw new RuntimeException("Not Implemented !");
            } catch (IndexOutOfBoundsException e) {
                return null;
            }
        }

        return o;
    } 

    public int getObjectSizeInBytes(Object o) {
        final String STRING_JAVA_TYPE_NAME = "java.lang.String";

        if (o == null)
            return 0;

        String objectType = o.getClass().getTypeName();
        boolean isArray = objectType.substring(objectType.length()-2).equals("[]");

        Object objRef = getFirstObjectReference(o);
        if (objRef != null && !(objRef instanceof Serializable))
            throw new RuntimeException("Object must be serializable for measuring it's memory footprint using this method !");

        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(o);
            oos.close();
            byte[] bytes = baos.toByteArray();

            for (int i = bytes.length - 1, j = 0; i != 0; i--, j++) {
                if (objectType != STRING_JAVA_TYPE_NAME) {
                    if (bytes[i] == 112)
                        if (isArray)
                            return j - 4;
                        else
                            return j;
                } else {
                    if (bytes[i] == 0)
                        return j - 1;
                }
            }
        } catch (Exception e) {
            return -1;
        }

        return -1;
    }    

}

Я тестировал это решение с примитивными типами String и на некоторых тривиальных классах. Могут быть и не покрытые случаи.


ОБНОВЛЕНИЕ: Пример, измененный для поддержки вычисления объема памяти для объектов массива.

0 голосов
/ 04 апреля 2013

Вы можете сгенерировать дамп кучи (например, с помощью jmap), а затем проанализировать вывод, чтобы найти размеры объектов. Это автономное решение, но вы можете исследовать мелкие и глубокие размеры и т. Д.

0 голосов
/ 03 марта 2015

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

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

Теперь Used heap memory = sizeOfObj + sizeOfRef (* 4 bytes) in collection

  • (4/8 байт) зависит от (32/64 бита) ОС

примитивы

int   [] intArray    = new int   [1]; will require 4 bytes.
long  [] longArray   = new long  [1]; will require 8 bytes.

ОБЪЕКТОВ

Object[] objectArray = new Object[1]; will require 4 bytes. The object can be any user defined Object.
Long  [] longArray   = new Long  [1]; will require 4 bytes.

Я имею в виду, что весь объект REFERENCE требует только 4 байта памяти. Это может быть ссылка на строку ИЛИ двойная ссылка на объект, но в зависимости от создания объекта требуемая память будет варьироваться.

например. Если я создаю объект для нижеуказанного класса ReferenceMemoryTest, то будет создано 4 + 4 + 4 = 12 байт памяти. Память может отличаться, когда вы пытаетесь инициализировать ссылки.

 class ReferenceMemoryTest {
    public String refStr;
    public Object refObj;
    public Double refDoub; 
}

Таким образом, при создании массива объектов / ссылок все его содержимое будет занято ссылками NULL. И мы знаем, что каждая ссылка требует 4 байта.

И, наконец, выделение памяти для приведенного ниже кода составляет 20 байт.

ReferenceMemoryTest ref1 = new ReferenceMemoryTest (); (4 (ref1) + 12 = 16 байт) ReferenceMemoryTest ref2 = ref1; (4 (ref2) + 16 = 20 байт)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...