Como obter o tamanho de um objeto em Java

Como obter o tamanho de um objeto em Java

1. Visão geral

Ao contrário do C/C ++, onde podemos usar o método sizeof () para obter um tamanho de objeto em bytes, não existe um equivalente verdadeiro desse método em Java.

Neste artigo, demonstraremos como ainda podemos obter o tamanho de um objeto específico.

2. Consumo de memória em Java

Embora não exista operador sizeof em Java, na verdade não precisamos de um. Todos os tipos primitivos têm um tamanho padrão e normalmente não há bytes de alinhamento ou pad. Ainda assim, isso nem sempre é simples.

*Embora os primitivos devam se comportar como se tivessem os tamanhos oficiais, uma JVM pode armazenar dados da maneira que desejar internamente, com qualquer quantidade de preenchimento ou sobrecarga* . Ele pode optar por armazenar um _booleano [] _ ​​em pedaços longos de 64 bits como _BitSet_, alocar alguns __Object__s temporários na pilha ou otimizar algumas variáveis ​​ou chamadas de método totalmente inexistentes, substituindo-as por constantes, etc.… Mas, desde que programa dá o mesmo resultado, está perfeitamente bem.

Levando também em conta o impacto dos caches de hardware e SO da conta (nossos dados podem ser duplicados em todos os níveis de cache), significa que podemos prever apenas aproximadamente o consumo de RAM .

2.1. Objetos, referências e classes de wrapper

*O tamanho mínimo do objeto é 16 bytes para o JDK moderno de 64 bits* , pois o objeto possui um cabeçalho de 12 bytes, preenchido com um múltiplo de 8 bytes. No JDK de 32 bits, a sobrecarga é de 8 bytes, preenchida com um múltiplo de 4 bytes.
*As referências têm um tamanho típico de 4 bytes em plataformas de 32 bits e plataformas de 64 bits* com limite de heap menor que 32 GB (_-Xmx32G_) e 8 bytes para esse limite acima de 32 GB.

Isso significa que uma JVM de 64 bits geralmente requer 30-50% mais espaço de heap.

Especialmente relevante é observar que os tipos de caixas, matrizes, Strings e outros contêineres, como matrizes multidimensionais, são dispendiosos em termos de memória, pois adicionam certa sobrecarga . Por exemplo, quando comparamos int primitivo (que consome apenas 4 bytes) ao objeto Integer que ocupa 16 bytes, vemos que há 300% de sobrecarga de memória.

3. Estimando o tamanho do objeto usando a instrumentação

Uma maneira de obter uma estimativa do tamanho de um objeto em Java é usar o método getObjectSize (Object) _ do https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html [_Instrumentation interface] introduzida no Java 5.

Como vimos na documentação do Javadoc, o método fornece "aproximação específica da implementação" do tamanho do objeto especificado. Vale ressaltar que existe uma possível inclusão de sobrecarga no tamanho e que os valores podem ser diferentes durante a chamada de JVM única.

*Essa abordagem suporta apenas a estimativa de tamanho do próprio objeto considerado e não o tamanho dos objetos que ele faz referência* . Para estimar um tamanho total do objeto, precisaríamos de um código que revelasse essas referências e calculasse o tamanho estimado.

3.1. Criando agente de instrumentação

Para chamar Instrumentation.getObjectSize (Object) _ para obter o tamanho do objeto, precisamos acessar a instância da Instrumentação primeiro. Precisamos usar o agente de instrumentação e há duas maneiras de fazê-lo, conforme descrito na documentação do https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/package -summary.html [_java.lang.instrument package.]

*O agente de instrumentação pode ser especificado via linha de comando ou podemos usá-lo com uma JVM* em execução. Vamos nos concentrar no primeiro.

Para especificar o agente de instrumentação por meio da linha de comandos , precisaremos da implementação do método premain sobrecarregado que será chamado pela JVM pela primeira vez ao usar a instrumentação. Além disso, precisamos expor um método estático para poder acessar _Instrumentation.getObjectSize (Object) _.

Vamos agora criar a classe InstrumentationAgent:

public class InstrumentationAgent {
    private static volatile Instrumentation globalInstrumentation;

    public static void premain(final String agentArgs, final Instrumentation inst) {
        globalInstrumentation = inst;
    }

    public static long getObjectSize(final Object object) {
        if (globalInstrumentation == null) {
            throw new IllegalStateException("Agent not initialized.");
        }
        return globalInstrumentation.getObjectSize(object);
    }
}
*Antes de criar um JAR para esse agente, precisamos garantir que um metarquivo simples, _MANIFEST.MF_ esteja incluído* :
Premain-class: com..objectsize.InstrumentationAgent

Agora podemos criar um JAR do agente com o arquivo MANIFEST.MF incluído. Uma maneira é via linha de comando:

javac InstrumentationAgent.java
jar cmf MANIFEST.MF InstrumentationAgent.jar InstrumentationAgent.class

3.2 Classe de exemplo

Vamos ver isso em ação, criando uma classe com objetos de amostra que farão uso da nossa classe de agente:

public class InstrumentationExample {

    public static void printObjectSize(Object object) {
        System.out.println("Object type: " + object.getClass() +
          ", size: " + InstrumentationAgent.getObjectSize(object) + " bytes");
    }

    public static void main(String[] arguments) {
        String emptyString = "";
        String string = "Estimating Object Size Using Instrumentation";
        String[] stringArray = { emptyString, string, "com." };
        String[] anotherStringArray = new String[100];
        List<String> stringList = new ArrayList<>();
        StringBuilder stringBuilder = new StringBuilder(100);
        int maxIntPrimitive = Integer.MAX_VALUE;
        int minIntPrimitive = Integer.MIN_VALUE;
        Integer maxInteger = Integer.MAX_VALUE;
        Integer minInteger = Integer.MIN_VALUE;
        long zeroLong = 0L;
        double zeroDouble = 0.0;
        boolean falseBoolean = false;
        Object object = new Object();

        class EmptyClass {
        }
        EmptyClass emptyClass = new EmptyClass();

        class StringClass {
            public String s;
        }
        StringClass stringClass = new StringClass();

        printObjectSize(emptyString);
        printObjectSize(string);
        printObjectSize(stringArray);
        printObjectSize(anotherStringArray);
        printObjectSize(stringList);
        printObjectSize(stringBuilder);
        printObjectSize(maxIntPrimitive);
        printObjectSize(minIntPrimitive);
        printObjectSize(maxInteger);
        printObjectSize(minInteger);
        printObjectSize(zeroLong);
        printObjectSize(zeroDouble);
        printObjectSize(falseBoolean);
        printObjectSize(Day.TUESDAY);
        printObjectSize(object);
        printObjectSize(emptyClass);
        printObjectSize(stringClass);
    }

    public enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }
}

Para que isso funcione, precisamos incluir a opção –javaagent com o caminho para o agente JAR ao executar nosso aplicativo :

VM Options: -javaagent:"path_to_agent_directory\InstrumentationAgent.jar"

O resultado da execução de nossa classe nos mostrará o tamanho estimado dos objetos:

Object type: class java.lang.String, size: 24 bytes
Object type: class java.lang.String, size: 24 bytes
Object type: class [Ljava.lang.String;, size: 32 bytes
Object type: class [Ljava.lang.String;, size: 416 bytes
Object type: class java.util.ArrayList, size: 24 bytes
Object type: class java.lang.StringBuilder, size: 24 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Long, size: 24 bytes
Object type: class java.lang.Double, size: 24 bytes
Object type: class java.lang.Boolean, size: 16 bytes
Object type: class com..objectsize.InstrumentationExample$Day, size: 24 bytes
Object type: class java.lang.Object, size: 16 bytes
Object type: class com..objectsize.InstrumentationExample$1EmptyClass, size: 16 bytes
Object type: class com..objectsize.InstrumentationExample$1StringClass, size: 16 bytes

4. Conclusão

Neste artigo, descrevemos como a memória é usada por tipos específicos em Java, como a JVM armazena dados e enfatiza coisas que podem afetar o consumo total de memória. Em seguida, demonstramos como podemos, na prática, obter o tamanho estimado dos objetos Java.

Como sempre, o código completo relacionado a este artigo pode ser encontrado no GitHub project.