1. Introdução: por que otimizar a serialização?
Em aplicativos modernos, a serialização está em toda parte — de protocolos de rede a caches distribuídos e troca de dados entre serviços.
A velocidade e o tamanho da serialização são críticos. Se a serialização for lenta, o aplicativo começa a “ficar lento” ao salvar ou carregar dados, e a rede ou o disco ficam ociosos à toa. Se os objetos ficarem grandes demais, ocupam muito espaço em disco, demoram mais para trafegar na rede e geram carga extra sobre memória e largura de banda.
Tarefas típicas incluem salvar um grande grafo de objetos em arquivo ou cache, transmitir objetos pela rede com latência mínima e serializar/desserializar dados rapidamente em um sistema multithread.
A conclusão é simples: otimizar a serialização não é um “recurso premium”, mas uma prática necessária para aplicações de alto desempenho e escaláveis.
2. Otimização do tamanho dos dados serializados
Excluir dados desnecessários: a palavra‑chave transient
Por padrão, todos os campos do objeto são serializados, exceto os marcados como transient. Se um campo não precisa ser salvo (por exemplo, cache, dados temporários, referências a serviços), marque-o como transient:
public class User implements Serializable {
private String name;
private transient String sessionToken; // não será serializado
}
Vantagens:
- Tamanho menor do objeto serializado.
- Sem dados desnecessários/perigosos no arquivo ou na rede.
Serialização manual: interface Externalizable
Se você precisa de controle total sobre o que e como é serializado, implemente a interface Externalizable e descreva explicitamente a serialização (métodos writeExternal/readExternal):
public class Person implements Externalizable {
private String name;
private int age;
private transient String secret;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
// não serializamos secret
}
@Override
public void readExternal(ObjectInput in) throws IOException {
name = in.readUTF();
age = in.readInt();
}
}
Vantagens:
- Apenas os campos necessários são serializados.
- Você pode mudar o formato de serialização sem perder compatibilidade.
Compressão: reduzir os dados serializados
Objetos serializados costumam ocupar muito espaço, especialmente se contêm strings repetidas e coleções grandes. É possível reduzir o tamanho com compressão.
Exemplo com GZIPOutputStream:
try (ObjectOutputStream out = new ObjectOutputStream(
new GZIPOutputStream(new FileOutputStream("data.gz")))) {
out.writeObject(bigObject);
}
Exemplo com ZipOutputStream:
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream("data.zip"))) {
zip.putNextEntry(new ZipEntry("object"));
ObjectOutputStream out = new ObjectOutputStream(zip);
out.writeObject(bigObject);
out.flush();
zip.closeEntry();
}
Vantagens:
- O tamanho do arquivo pode diminuir várias vezes (especialmente para grafos grandes de objetos).
- Menos tráfego na transmissão pela rede.
Desvantagens:
- Comprimir/descompactar exige tempo adicional (CPU).
3. Otimização da velocidade de serialização
Bufferização: por que usar BufferedOutputStream e BufferedInputStream
Problema:
Sem bufferização, cada chamada write() ou read() resulta em acesso do sistema ao disco ou à rede — isso é muito lento!
Solução:
Use streams com buffer:
try (ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data.bin")))) {
out.writeObject(bigObject);
}
try (ObjectInputStream in = new ObjectInputStream(
new BufferedInputStream(new FileInputStream("data.bin")))) {
Object obj = in.readObject();
}
Vantagens:
- Acelera significativamente a escrita/leitura de objetos grandes.
- Reduz a quantidade de acessos ao disco/rede.
Como isso funciona?
O buffer acumula dados na memória e os grava em blocos, em vez de byte a byte.
Cópia rápida: FileChannel.transferTo
Se você precisa copiar rapidamente um arquivo serializado grande, use NIO e o método transferTo:
try (FileChannel src = new FileInputStream("data.bin").getChannel();
FileChannel dest = new FileOutputStream("copy.bin").getChannel()) {
src.transferTo(0, src.size(), dest);
}
Vantagens:
- A cópia ocorre no nível do SO, contornando buffers extras no Java — muito rápido para arquivos grandes.
4. Perfilamento da serialização
Medição simples de tempo: System.nanoTime()
Para uma avaliação rápida do desempenho da serialização, você pode usar System.nanoTime():
long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data.bin")))) {
out.writeObject(bigObject);
}
long end = System.nanoTime();
System.out.println("Tempo de serialização: " + (end - start) / 1_000_000 + " ms");
Vantagens:
- Simples e rápido.
- Permite comparar diferentes variantes (com buffer, sem buffer, com compressão etc.).
Desvantagens:
- Os resultados podem “oscilar” devido ao trabalho do GC e a processos em segundo plano.
- Não é adequado para comparar diferenças microscópicas com precisão.
Perfilamento preciso: JMH (Java Microbenchmark Harness)
Para medições mais precisas, use JMH — uma biblioteca específica para microbenchmarks.
Exemplo de benchmark simples:
@Benchmark
public void serializeWithBuffer() throws Exception {
try (ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data.bin")))) {
out.writeObject(bigObject);
}
}
Vantagens:
- Considera o aquecimento da JVM, a influência do GC e os “ruídos” do SO.
- Fornece resultados confiáveis e reproduzíveis.
Desvantagens:
- Exige configuração e entendimento da metodologia do JMH.
- Exagero para comparações “a olho”.
5. Prática: comparar tempo e tamanho da serialização
Vamos fazer um mini‑experimento: serializar um grande grafo de objetos (por exemplo, uma lista de 100_000 objetos com coleções aninhadas) de diferentes maneiras e comparar o tempo e o tamanho do arquivo.
Serialização sem bufferização e sem compressão
long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data1.bin"))) {
out.writeObject(bigList);
}
long end = System.nanoTime();
System.out.println("Sem buffer: " + (end - start) / 1_000_000 + " ms, tamanho: " +
new File("data1.bin").length() + " bytes");
Serialização com bufferização
long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data2.bin")))) {
out.writeObject(bigList);
}
long end = System.nanoTime();
System.out.println("Com buffer: " + (end - start) / 1_000_000 + " ms, tamanho: " +
new File("data2.bin").length() + " bytes");
Serialização com compressão (GZIP)
long start = System.nanoTime();
try (ObjectOutputStream out = new ObjectOutputStream(
new GZIPOutputStream(new FileOutputStream("data3.gz")))) {
out.writeObject(bigList);
}
long end = System.nanoTime();
System.out.println("Com compressão: " + (end - start) / 1_000_000 + " ms, tamanho: " +
new File("data3.gz").length() + " bytes");
Análise dos resultados
Nos testes de serialização, fica nítido o quanto a bufferização e a compressão influenciam. Os arquivos compactados geralmente ficam de 2–10 vezes menores (o fator exato depende da estrutura dos dados). Com buffer, a serialização fica significativamente mais rápida, e a compressão deixa o processo um pouco mais lento, mas a economia de espaço costuma valer a pena.
Conclusão: para grandes volumes de dados, use obrigatoriamente bufferização; se o tamanho for crítico, ative a compressão.
6. Erros comuns ao otimizar a serialização
Erro nº 1: Não usar bufferização — a serialização de objetos grandes fica várias vezes mais lenta.
Erro nº 2: Serializar dados desnecessários ou sensíveis (por exemplo, senhas, tokens temporários) — sempre use transient para esses campos.
Erro nº 3: Esperar que a compressão sempre acelere a serialização — na verdade, a compressão reduz o tamanho, mas pode deixá-la um pouco mais lenta (especialmente em CPUs fracas).
Erro nº 4: Medir o tempo sem considerar o aquecimento da JVM e a influência do GC — para benchmarks precisos, use o JMH.
Erro nº 5: Comparar apenas o tempo ou apenas o tamanho — sempre observe ambos os parâmetros para escolher o equilíbrio ideal para o seu caso.
GO TO FULL VERSION