Tamanhos de diferentes tipos de objetos Java

1. Introdução


O objeto Java contém:

  • campos declarados na superclasse?
  • campos privados declarados em uma superclasse?
  • métodos?
  • elementos da matriz?
  • comprimento da matriz?
  • outro objeto (em si)?
  • código hash?
  • tipo (próprio)?
  • nome (próprio)?

As respostas para essas (e outras) perguntas podem ser obtidas usando a biblioteca de classes org.openjdk.jol , que, em particular, permite entender que o objeto é uma área de memória:

  • contendo:
    • cabeçalho (até 16 bytes) e nele:
      • código hash
      • tipo de referência
      • comprimento da matriz (para matriz)
    • todos os campos (incluindo particulares) declarados em todas as superclasses
    • ou elementos da matriz (para uma matriz)
  • não contém:
    • variáveis ​​estáticas
    • métodos
    • outros objetos em si mesmo
    • próprio nome (ou seja, o objeto não tem um nome)


Preparação


Aqui estão os resultados da avaliação da memória de objetos de vários tipos pelo método a partir da descrição do pacote java.lang.instrument (veja também aqui ). Esses resultados nos permitem responder à maioria das perguntas colocadas acima.

As etapas a seguir devem ser concluídas:

  1. Crie uma classe de agente contendo o método premain:
    public static void premain(String, Instrumentation) {...} 
  2. Crie um archive contendo a classe do agente e o arquivo de manifesto com o conteúdo:
     Premain-class: -- 
  3. Crie uma classe executável para avaliar a memória.
  4. Especifique o archive com o parâmetro "-javaagent" ao iniciar a máquina virtual:
     java -javaagent:- -- 


Vamos começar com um caso de teste. Para simplificar, usamos um pacote sem nome.

Etapa 1. Crie uma classe de agente de avaliação


 import java.lang.instrument.Instrumentation; public class A { public static void premain(String notUsedHere, Instrumentation i) { System.out.println("premain"); } } 

Nós compilamos:

 javac A.java 

Etapa 2. Crie um arquivo de manifesto m.txt contendo:


 Premain-class: A   

ATENÇÃO: a segunda linha do arquivo deve ser VAZIA, NÃO CONTENDO ESPAÇOS.

Crie o arquivo A.jar:

 jar cmf m.txt A.jar A.class 

Etapa 3. Crie uma classe executável de avaliação


 public class M { public static void main(String[] notUsedHere) { //     System.out.println("main"); } } 

Nós compilamos:

 javac M.java 

Etapa 4. Execute


 java -javaagent:A.jar M 

Resultado:
 premain main 

indica que o método premain da classe do agente foi chamado primeiro e, em seguida, o método principal da classe executável.

Agora crie a classe de agente necessária:

 import java.lang.instrument.Instrumentation; public class A { //      private static Instrumentation ins; public static void premain(String notUsedHere, Instrumentation i) { ins = i; } public static Instrumentation instrumentation() {return ins;} } 

e classe executável:

 class M { public static void main(String[] notUsedHere) { mem("Object", new Object()); } private static void mem(Object o, Object ref) { System.out.println(o + ": " + objectBytesEstimate(ref)); } private static long objectBytesEstimate(Object ref) { if (A.instrumentation() == null) { throw new RuntimeException("Not initialized instrumentation."); } return A.instrumentation().getObjectSize(ref); } } 

Método

 long getObjectSize(Object --) 

retorna uma ESTIMAÇÃO do tamanho (número de bytes) de memória ocupada pelo objeto no link especificado. Deve-se ter em mente que a estimativa resultante pode ser diferente para outra máquina virtual. Os valores para jdk-13 serão listados aqui .

Realizamos:

 javac *.java jar cmf m.txt A.jar A.class java -javaagent:A.jar M 

e obtemos o resultado:

 Object: 16 

mostrando que um objeto VAZIO do tipo Objeto ocupa aqui (POR AVALIAÇÃO) 16 bytes. Desses, 12 bytes ocupam o cabeçalho e 4 bytes no final servem para alinhar o comprimento do objeto ao limite de 8 bytes.

Resultados


Outros exemplos conterão apenas o código colocado no método principal da classe M. Eles devem ser executados para cada exemplo com os comandos:

 javac M.java java -javaagent:A.jar M 

Não é necessário recriar o A.jar.

Por exemplo, para obter uma estimativa do tamanho da memória de um objeto de um tipo arbitrário sem campos, colocamos o código no método principal:

 class C {}; mem("Empty", new C()); // Empty: 16 

O resultado indicado no comentário mostra que um objeto sem campos ocupa tantos bytes quanto um objeto do tipo Object.

Além disso, o resultado do programa:

 {class C {int a; } mem(1, new C());} // 1: 16 {class C {int a,b; } mem(2, new C());} // 2: 24 {class C {int a,b,c; } mem(3, new C());} // 3: 24 {class C {int a,b,c,d;} mem(4, new C());} // 4: 32 

indica que cada campo int ocupa 4 bytes. Observo que aqui cada linha é um bloco separado, o que permite que você use o mesmo nome para diferentes classes.

Cada campo longo é de 8 bytes:

 {class C {long a; } mem(1, new C());} // 1: 24 {class C {long a,b; } mem(2, new C());} // 2: 32 {class C {long a,b,c;} mem(3, new C());} // 3: 40 

Cada campo booleano recebe 1 byte (para esta VM):

 {class C {boolean a; } mem(1, new C());} // 1: 16 {class C {boolean a,b; } mem(2, new C());} // 2: 16 {class C {boolean a,b,c; } mem(3, new C());} // 3: 16 {class C {boolean a,b,c,d; } mem(4, new C());} // 4: 16 {class C {boolean a,b,c,d,e;} mem(5, new C());} // 5: 24 

Cada campo de referência ocupa 4 bytes (para esta VM):

 {class C {Boolean a; } mem(1, new C());} // 1: 16 {class C {Integer a; } mem(1, new C());} // 1: 16 {class C {Long a; } mem(1, new C());} // 1: 16 {class C {C a; } mem(1, new C());} // 1: 16 {class C {Boolean a,b; } mem(2, new C());} // 2: 24 {class C {Integer a,b; } mem(2, new C());} // 2: 24 {class C {Long a,b; } mem(2, new C());} // 2: 24 {class C {C a,b; } mem(2, new C());} // 2: 24 {class C {Boolean a,b,c; } mem(3, new C());} // 3: 24 {class C {Integer a,b,c; } mem(3, new C());} // 3: 24 {class C {Long a,b,c; } mem(3, new C());} // 3: 24 {class C {C a,b,c; } mem(3, new C());} // 3: 24 {class C {Boolean a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Integer a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Long a,b,c,d;} mem(4, new C());} // 4: 32 {class C {C a,b,c,d;} mem(4, new C());} // 4: 32 

Um campo do tipo String também possui 4 bytes, como todas as referências:

 {class C {String a; } mem(" null", new C());} // null: 16 {class C {String a=""; } mem(" empty", new C());} // empty: 16 {class C {String a="A"; } mem("1-char", new C());} // 1-char: 16 {class C {String a="1234567";} mem("7-char", new C());} // 7-char: 16 

Um campo de referência de matriz também ocupa 4 bytes, como todo referência:

 {class C {int[] a; } mem("null", new C());} // null: 16 {class C {int[] a = {}; } mem(" 0", new C());} // 0: 16 {class C {int[] a = new int[1]; } mem(" 1", new C());} // 1: 16 {class C {int[] a = new int[7]; } mem(" 7", new C());} // 7: 16 {class C {int[][] a = {}; } mem(" 00", new C());} // 00: 16 {class C {int[][] a = new int[1][1];} mem(" 11", new C());} // 11: 16 {class C {int[][] a = new int[7][7];} mem(" 77", new C());} // 77: 16 

O objeto subtipo contém cada campo declarado na superclasse, independentemente do modificador de acesso:

 {class S { } class C extends S {long a;} mem("0+1", new C());} // 0+1: 24 {class S {private long a;} class C extends S { } mem("1+0", new C());} // 1+0: 24 

O objeto subtipo contém um campo declarado na superclasse com o mesmo nome da subclasse (o chamado hidden - hidden):

 {class S { } class C extends S {long a,b;} mem("0+2", new C());} // 0+2: 32 {class S {long a;} class C extends S {long a; } mem("1+1", new C());} // 1+1: 32 

Um objeto de subtipo contém cada campo declarado em cada uma de suas superclasses:

 class U {private long a; } class S extends U {private long a; } class C extends S { long a; } mem("1+1+1", new C()); // 1+1+1: 40 class D { long a,b,c;} mem("0+0+3", new D()); // 0+0+3: 40 

Vire para matrizes. Como você sabe, uma matriz é um tipo especial de objeto cujos elementos estão no próprio objeto; portanto, o tamanho da memória ocupada pela matriz aumenta com o número de elementos:

 {long[] a = new long[ 0]; mem(" 0", a);} // 0: 16 {long[] a = new long[ 1]; mem(" 1", a);} // 1: 24 {long[] a = new long[ 2]; mem(" 2", a);} // 2: 32 {long[] a = new long[ 3]; mem(" 3", a);} // 3: 40 {long[] a = new long[100]; mem("100", a);} // 100: 816 

E para a variedade de links:

 {Long[] a = new Long[ 0]; mem(" 0", a);} // 0: 16 {Long[] a = new Long[ 1]; mem(" 1", a);} // 1: 24 {Long[] a = new Long[ 2]; mem(" 2", a);} // 2: 24 {Long[] a = new Long[ 3]; mem(" 3", a);} // 3: 32 {Long[] a = new Long[100]; mem("100", a);} // 100: 416 

Agora, por curiosidade, comparamos os tamanhos de vários objetos de diferentes tipos:

 mem(" Object", new Object()); // Object: 16 mem(" String", new String("ABC")); // String: 24 mem(" Exception", new Exception()); // Exception: 40 mem(" int.class", int.class); // int.class: 112 mem(" int[].class", int[].class); // int[].class: 112 mem("Object.class", Object.class); // Object.class: 112 mem("System.class", System.class); // System.class: 160 mem("String.class", String.class); // String.class: 136 

O mesmo para o jdk diferente em um processador de 64 bits:

                 jdk1.6.0_45 jdk1.7.0_80 jdk1.8.0_191 jdk-9 jdk-12 jdk-13
                 ----------- ----------- ------------ ------ ------ ---- -
       Objeto: 16 16 16 16 16 16
       String: 32 24 24 24 24 24
    Exceção: 32 32 32 40 40 40
    int.class: 104 88 104 112 104 112
  int []. classe: 584 544 480 112 104 112
 Classe de objeto: 600 560 496 112 104 112
 System.class: 624 560 496 144 152 160
 String.class: 696 640 624 136 128 136 

A estimativa do tamanho da String é de 24 bytes, embora a classe String contenha muitas variáveis ​​estáticas, métodos estáticos e não estáticos. Indubitavelmente, isso indica a ausência de variáveis ​​estáticas e código do método no objeto. O mesmo vale para um objeto de qualquer tipo.

Concluindo, um lembrete: todos os dados sobre o tamanho do objeto são estimados e podem variar em certa medida de uma execução para outra e, é claro, para diferentes máquinas virtuais.

Source: https://habr.com/ru/post/pt470441/


All Articles