[RESOLVIDO] Gravação de arquivo, erro java.lang.OutOfMemoryError

6 respostas
L

Olá, embora este seja meu primeiro post aki, já consulto este fórum há algum tempo. Sou bastante novato em java e não tenho alguém experiente para perguntar, então é aqui que me refugio para sanar minhas dúvidas e tento ser muito persistente até achar o que quero. Acontece que agora estou com um problema do qual não estou conseguindo encontrar a melhor solução. É meu primeiro aplicativo java oficial e preciso gerar um pdf com o resultado de um log de impressão previamente lido e exibido na tela. Ele funciona muito bem até uma certa quantidade de resultados, mas a partir de um certo ponto ele da o erro “java.lang.OutOfMemoryError”. Consegui tranquilamente criar um pdf com umas 700 páginas, mas acima disto começa a dar o erro. Sei que alguns podem me questionar sobre o porque de um pdf tão grande, e já adianto que realmente dificilmente precisarei de mais do que isto, acontece que o além de existir uma possibilidade, embora muito remota, tem uma outra questão, por ser novato sei que meu código pode melhorar e além disto será um bom aprendizado com o I/O pois sei que se mal escrito, pode comprometer muito uma aplicação. Eu sei que a melhor forma é jogar para um BufferOutputStream e assim o fiz de uma maneira simples mas que não resolveu o problema, acredito que porque embora com o buffer, na hora de gravar, continua tentando gravar tudo de uma vez. Gostaria de saber como poderia fazer para ir gravando aos poucos, tipo, estipular um tamanho para o buffer e quando ele estivesse com aquele tamanho, ir jogando para o arquivo, e quando terminasse, fecho o arquivo. Saber como trabalhar isto seria de grande valia, não só para este, como para outros aplicativos futuros.

Segue o código:

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Font.FontFamily;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;

import model.Log;

public class GerarPDFLogImpressoes {

	private PdfPTable tabela;
	private PdfPCell celula;

	private Font fonteCabecalho;
	private Font fonteDados;

	PdfWriter writer = null;
	private BufferedOutputStream bos;
	private FileOutputStream os;
	private Document documento;

	public GerarPDFLogImpressoes(ArrayList<Log> logs, String qtdeImpressoes) {
		try {

			// Cria uma nova instancia PdfWriter com os paramentos do documento
			// e do buffer de saida
			writer = PdfWriter.getInstance(getDocumento(), getBos());

			// Abre o documento para edicao
			getDocumento().open();

			// Adicionar a linha do cabecalho
			definirCabecalho();

			// Faz uma varredura do array de logs e vai adicionando a tabela
			for (Log log : logs) {
				getTabela().addCell(getCelula(new SimpleDateFormat("dd/MM/yyyy HH:mm").format(log.getTime()), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getUser(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getClient(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getDocumentName(), Element.ALIGN_LEFT, getFonteDados()));
				getTabela().addCell(
						getCelula(NumberFormat.getInstance().format(((long) log.getPages() * (long) log.getCopies())), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getPrinter(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getPaperSize(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getGrayscale(), Element.ALIGN_CENTER, getFonteDados()));
			}

			// Adiciona o rodape ao final da tabela
			definirRodape(qtdeImpressoes);

			// Adiciona a tabela criada ao documento aberto
			getDocumento().add(getTabela());

			// Trata os erros
		} catch (DocumentException e) {
			System.out.println(e.getMessage());

			// Finaliza o arquivo
		} finally {

			// Finaliza o documento
			if (getDocumento() != null) {
				getDocumento().close();
			}

			// Finaliza o buffer
			if (getBos() != null) {
				try {
					getBos().close();

					// Abre o arquivo recem criado
					java.awt.Desktop.getDesktop().open(new File("Relatório de impressão.pdf"));

					// Trata os erros
				} catch (IOException e) {
					System.out.println(e.getMessage());
				}
			}
		}
	}

	private BufferedOutputStream getBos() {
		if (bos == null) {
			bos = new BufferedOutputStream(getOs());
		}
		return bos;
	}

	private FileOutputStream getOs() {
		if (os == null) {
			try {
				os = new FileOutputStream("Relatório de impressão.pdf");
			} catch (FileNotFoundException e) {
				System.out.println(e.getMessage());
			}
		}
		return os;
	}

	// Trata o documento
	private Document getDocumento() {
		if (documento == null) {
			documento = new Document(PageSize.A4.rotate(), 20, 20, 20, 20);
		}
		return documento;
	}

	// Trata a fonte do cabecalho
	private Font getFonteCabecalho() {
		if (fonteCabecalho == null) {
			fonteCabecalho = new Font(FontFamily.HELVETICA, 11, Font.BOLD);
		}
		return fonteCabecalho;
	}

	// Trata a fonte do rodape
	private Font getFonteRodape() {
		if (fonteCabecalho == null) {
			fonteCabecalho = new Font(FontFamily.HELVETICA, 10, Font.BOLD);
		}
		return fonteCabecalho;
	}

	// Trata a fonte dos dados
	private Font getFonteDados() {
		if (fonteDados == null) {
			fonteDados = new Font(FontFamily.HELVETICA, 9);
		}
		return fonteDados;
	}

	// Adiciona a linha do cabecalho
	private void definirCabecalho() {
		getTabela().addCell(getCelula("Data / Hora", Element.ALIGN_CENTER, getFonteCabecalho()));
		getTabela().addCell(getCelula("Usuário", Element.ALIGN_CENTER, getFonteCabecalho()));
		getTabela().addCell(getCelula("Computador", Element.ALIGN_CENTER, getFonteCabecalho()));
		getTabela().addCell(getCelula("Nome do documento", Element.ALIGN_CENTER, getFonteCabecalho()));
		getTabela().addCell(getCelula("Impressões", Element.ALIGN_CENTER, getFonteCabecalho()));
		getTabela().addCell(getCelula("Impressora", Element.ALIGN_CENTER, getFonteCabecalho()));
		getTabela().addCell(getCelula("Papel", Element.ALIGN_CENTER, getFonteCabecalho()));
		getTabela().addCell(getCelula("Cor", Element.ALIGN_CENTER, getFonteCabecalho()));
	}

	// Adiciona a linha do rodape
	private void definirRodape(String qtdeImpressoes) {
		PdfPCell celulaRodape = getCelula("Total de impressões: " + qtdeImpressoes, Element.ALIGN_RIGHT, getFonteRodape());
		celulaRodape.setColspan(9);
		celulaRodape.setBorder(PdfPCell.NO_BORDER);
		getTabela().addCell(celulaRodape);
	}

	// Trata a tabela
	private PdfPTable getTabela() {
		if (tabela == null) {
			tabela = new PdfPTable(new float[] { 0.100f, 0.125f, 0.125f, 0.350f, 0.100f, 0.120f, 0.045f, 0.035f });
			tabela.setWidthPercentage(100f);
		}
		return tabela;
	}

	// Trata a celula
	private PdfPCell getCelula(String texto, int alinhamento, Font fonte) {
		celula = new PdfPCell(new Phrase(texto, fonte));
		celula.setMinimumHeight(15f);
		celula.setHorizontalAlignment(alinhamento);
		celula.setVerticalAlignment(Element.ALIGN_MIDDLE);
		return celula;
	}

}

6 Respostas

jaboot

Cara, 700 páginas pode ser muita coisa para a memória mesmo… Nunca criei um pdf, mas se você puder escrever umas 10 páginas, fechar o arquivo e abrir ele de novo e escrever mais 10, acho que fica mais sussa pra você e para o computador.

Tente rodar o seu programa com o jvisualvm aberto (se windows, abre um cmd e digita jvisualvm). Ele vai te dar uma amostra de quanto está usando e quando é que estoura.

L

Sim, claro, mas é como eu disse, achei uma boa oportunidade de aprender melhor sobre I/O e BufferOutput e estou usando isto como um exemplo. Acho que se ir salvando o buffer aos poucos é uma solução que funcionaria como se salvasse a cada 10 páginas só que não estou entendendo como fazer isto.

jaboot

Isso mesmo brother, sempre há um jeito de aprender e fazer a coisa funcionar.

O que eu posso te aconselhar é procurar alguma biblioteca que permita gravação do arquivo, e edição dele depois. Você sabe que o que esta causando o estouro é aquele objeto sendo alimentado dentro do loop, não ?

Fazendo em partes, além de tornar a execução mais rápida, te permite criar um pdf de n páginas. Talvez tenha problema na hora de abrir o arquivo para edição por causa do tamanho que ele possa estar, mas deve ter algo que appenda a página no final do arquivo sem abrir. ele .

mantenha-nos informados!

edit: acabei de pensar… e se você gravasse tudo em um txt e depois transformasse ele em um pdf?!

L

Assim, a minha maior dúvida na verdade é como trabalhar com o buffer para ir salvando aos poucos. O iText (biblioteca que gera o pdf) tem métodos para ir incrementando o pdf, ou seja, abrir o que eu já tenho e ir adicionando as páginas, mas acontece que eu quero pensar adiante, e quando for outro caso que não seja o pdf? Tipo, ao invés de contornar fazendo a biblioteca ir adicionando paginas ao pdf, eu queria descobrir como acabar com este estouro de memória trabalhando com as ferramentas do java mesmo. Como eu acredito que o estouro é devido ao tamanho do arquivo, como fazer para o buffer ir salvando e descarregando a memória aos poucos, ao invés de ir gerando o pdf, fechar, abrir, adicionar paginas, fechar, abrir… Não sei se consegui ser claro. Com relação ao tamanho do ArrayList, eu trabalho com ele dentro de uma JTable numa outra parte do aplicativo e não da estouro de memória, então não acredito ser ele o problema.

L

Cara, resolvido. Fiz um debug bem minuncioso, estudei um pouco sobre buffer e sobre o código do programa e a solução foi extremamente simples. Eu vi que a demora era ao adicionar a tabela no documento (o bloco do for executava extremamente rápido), eu já tinha tentando adicionar a tabela ao documento, reabrir e começar a adicionar registros de novo, mas dava uma quebra na tabela, então, numa pesquisa pelo google achei um cara reclamando que após uma mudança que teve há uns anos atras, na versão 1.2 (hoje esta na 1.5) o consumo de memória tinha aumentado muito ao trabalhar com tabelas e lá achei uma método da tabela que não fechava ela, apenas zerava o que já tinha, juntando isto ao metodo de adicionar a tabela ao documento antes de zerar, ele da continuidade de forma correta.

O código que implementei foi no meio do bloco “for (Log log : this.logs) {” ficando assim:

// Faz uma varredura do array de logs e vai adicionando a tabela
			for (Log log : this.logs) {
				getTabela().addCell(getCelula(new SimpleDateFormat("dd/MM/yyyy HH:mm").format(log.getTime()), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getUser(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getClient(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getDocumentName(), Element.ALIGN_LEFT, getFonteDados()));
				getTabela().addCell(getCelula(NumberFormat.getInstance().format(((long) log.getPages() * (long) log.getCopies())), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getPrinter(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getPaperSize(), Element.ALIGN_CENTER, getFonteDados()));
				getTabela().addCell(getCelula(log.getGrayscale(), Element.ALIGN_CENTER, getFonteDados()));
				if (getTabela().size() == 50) {
					getDocumento().add(getTabela());
					getTabela().deleteBodyRows();
				}
			}

Resultado final:

Registros: 39.134

Tempo total de processamento: 225.541s
Tamanho total do PDF: 4,09MB

Tempo total de processamento: 7.94s
Tamanho total do PDF: 4,15MB

J

Olá Leduck. Passando para agradecer por compartilhar sua solução. Resolveu pra mim, consegui imprimir 3.000 páginas. Antes conseguia imprimir no máximo 500.

Obrigado mesmo!

Criado 23 de outubro de 2012
Ultima resposta 26 de jul. de 2014
Respostas 6
Participantes 3