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:
- Crie uma classe de agente contendo o método premain:
public static void premain(String, Instrumentation) {...}
- Crie um archive contendo a classe do agente e o arquivo de manifesto com o conteúdo:
Premain-class: --
- Crie uma classe executável para avaliar a memória.
- 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) {
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 {
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());
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());}
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());}
Cada campo booleano recebe 1 byte (para esta VM):
{class C {boolean a; } mem(1, new C());}
Cada campo de referência ocupa 4 bytes (para esta VM):
{class C {Boolean a; } mem(1, new C());}
Um campo do tipo String também possui 4 bytes, como todas as referências:
{class C {String a; } mem(" null", new C());}
Um campo de referência de matriz também ocupa 4 bytes, como todo referência:
{class C {int[] a; } mem("null", new C());}
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());}
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());}
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());
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);}
E para a variedade de links:
{Long[] a = new Long[ 0]; mem(" 0", a);}
Agora, por curiosidade, comparamos os tamanhos de vários objetos de diferentes tipos:
mem(" Object", new Object());
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.