Our program has a batch PDF exporting feature that exports several PDF's of 1-3 page maps into a folder. It works well but there is an overlying issue that after a 1 map gets exported, there is permanent memory added to Java's heap. So if our program is exporting 25+ maps, the program throws an out of heap Memory exception and no more maps can be exported (On top of crashing the program). I used Visual VM's heap analyzer and discovered that there are lots of instances of elements from .apache.batik.dom.GenericText and .apache.batik.dom.GenericAttr that aren't getting cleared from the heap. I have a work around in my program that measures system memory and stops the printing process if too much memory has been consumed, and then printing resumes when the user logs back in after the crash. This work around works but is only a temporary solution for our clients. Is there any way to clear those elements from the heap directly, or are there any suggestions on where to look to prevent this memory leak?
I've tried reviewing the code to see if there are any open transcoder's/stream's and I don't think that's the case but I could be wrong.
public void printBatchToPDF(final BatchPrint bp, final boolean combine) {
final ArrayList<String> usedFileNames = new ArrayList();
final UpdateProgressDialog progress = new UpdateProgressDialog(this, "Creating PDF");
final MainMAPSPanel main = this;
Runtime runtime = Runtime.getRuntime();
System.setProperty(".apache.batik.warn_destination_cache", "false");
//
initFileChooser();
jfc.setDialogTitle("Save PDFs To Folder");
UIManager.put("FileChooser.fileNameLabelText", "Folder:");
SwingUtilities.updateComponentTreeUI(jfc);
int choice = jfc.showSaveDialog(null);
if (choice != JFileChooser.APPROVE_OPTION) {
return;
}
File file = null;
final String absPath = jfc.getSelectedFile().getAbsolutePath();
// absPath = jfc.getSelectedFile().getAbsolutePath();
file = new File(absPath);
file.mkdir();
SwingWorker worker = new SwingWorker() {
@Override
protected Object doInBackground() {
int index = 0;
for (PrintView pv : bp.getViews()) {
////////
ArrayList<String> fileNames = new ArrayList();
try {
progress.setMainText("Exporting Print Views " + ++index + " OF " + bp.getViews().size());
PageFormat pageFormat = null;
PrinterJob printerJob = null;
Drawing draw = pv.getDrawing();
// if (draw.getLayers() == null || draw.getLayers().isEmpty()) {
draw = DrawingDA.findDrawing(project, draw.getDrawingName(), getConnection());
LayerSet ls = LayerSetDA.find(pv.getLayerset().getSetName(), draw, getConnection());
ArrayList<String> onLayers = new ArrayList(ls.getLayers().size());
ArrayList<String> shadowLayers = new ArrayList(ls.getLayers().size());
for (int i = 0; i < ls.getLayers().size(); i++) {
Layer l = ls.getLayers().get(i);
boolean on = ls.getVisible().get(i);
boolean shadowed = ls.getShadowed().get(i);
if (on) {
onLayers.add(l.getLayerName());
}
if (shadowed) {
shadowLayers.add(l.getLayerName());
}
}
for (Layer l : draw.getLayers()) {
if (onLayers.contains(l.getLayerName())) {
l.isVisible(true);
}
if (shadowLayers.contains(l.getLayerName())) {
l.isShadowed(true);
}
}
final Drawing drawing = draw;
File file = null;
String absPath = jfc.getSelectedFile().getAbsolutePath();
file = new File(absPath);
//SVGDocument doc = null;
LayerSet l = pv.getLayerset();
ArrayList<String> colorCodeNames = ColorCodeDA.getLayerSetColorCodes(l, getConnection());
ArrayList<ColorCode> ccs = new ArrayList(colorCodeNames.size());
if (getActiveDrawingPanel() != null) {
Iterator<ColorCode> iter = getActiveDrawingPanel().getColorCodes().iterator();
//ArrayList<ColorCode> ccs2 = getActiveDrawingPanel().getColorCodes();
while(iter.hasNext()) {
ColorCode c = iter.next();
if (!c.isRunning()) {
iter.remove();
} else if (!colorCodeNames.contains(c.getColorCodeName())) {
colorCodeNames.add(c.getColorCodeName());
}
}
iter = null;
}
ArrayList<ArrayList<Long>> elementids = new ArrayList();
for (String s : colorCodeNames) {
ColorCode c = ColorCodeDA.find(project, s, getConnection());
ColorCodeQueryDA.findAll(c, getConnection());
for (ColorCodeQuery ccq : c.getColorCodeQueries()) {
ArrayList<Long> ids = ColorCodeQueryDA.findAffectedElementIDs(new DrawingPanel(pv.getDrawing()), ccq, getConnection());
elementids.add(ids);
}
ccs.add(c);
}
//final SVGDocument tempSVGDocument = doc;
ArrayList<Rectangle2D> aoiRect = new ArrayList();
for (PrintViewPage pvp : pv.getPages()) {
Rectangle2D r = pvp.getRect();
aoiRect.add(r);
}
if (printerJob == null) {
printerJob = PrinterJob.getPrinterJob();
}
//if page fomrat is not set then use default page format
pageFormat = printerJob.defaultPage();
//ArrayList<String> addedNames = new ArrayList();
//ArrayList<Printable> prints = new ArrayList();
// ArrayList<String> fileNames = new ArrayList();
for (int p = 0; p < pv.getPages().size(); p++) {
PrintViewPage pvp = pv.getPages().get(p);
String date = "";
String name = "";
String scale = "";
if (pvp.getRect().getWidth() > pvp.getRect().getHeight()) {
pageFormat.setOrientation(PageFormat.LANDSCAPE);
} else {
pageFormat.setOrientation(PageFormat.PORTRAIT);
}
if (pvp.getSheetName() != null) {
name = pvp.getSheetName();
}
if (pvp.isToScale()) {
scale = " Scale: " + pvp.getScaleString();
}
if (pvp.isShowDate()) {
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
date = sdf.format(new Date());
}
/*
double lM = pvp.getLeftMargin();
double rM = pvp.getRightMargin();
double tM = pvp.getTopMargin();
double bM = pvp.getBottomMargin();
*/
Paper pagePaper = pageFormat.getPaper();
pagePaper.setSize(pvp.getPageWidthHeight()[0], pvp.getPageWidthHeight()[1]);
pageFormat.setPaper(pagePaper);
//
Transcoder pdfTranscoder = new PDFTranscoder();
Rectangle tempAoiRect;
tempAoiRect = new Rectangle((int) aoiRect.get(p).getX(), (int) aoiRect.get(p).getY(), (int) aoiRect.get(p).getWidth(), (int) aoiRect.get(p).getHeight());
double borderWidth = 0;
pdfTranscoder.addTranscodingHint(PDFTranscoder.KEY_WIDTH, (float) pvp.getPageWidthHeight()[1] * 96);
pdfTranscoder.addTranscodingHint(PDFTranscoder.KEY_HEIGHT, (float) pvp.getPageWidthHeight()[0] * 96);
pdfTranscoder.addTranscodingHint(PDFTranscoder.KEY_AOI, tempAoiRect);
Rectangle2D.Double rect = new Rectangle2D.Double(tempAoiRect.getX(), tempAoiRect.getY(), tempAoiRect.getWidth(), tempAoiRect.getHeight());
SVGDocument minimal = PrintSVGDA.buildMinimalSVG(drawing, rect);
SVGUtilities.addStringToPrint(pvp, scale, date, name, minimal, tempAoiRect);
tempAoiRect = null;
rect = null;
//
int startQuery = 0;
for (int i = 0; i < ccs.size(); i++) {
int queryCount = ccs.get(i).getColorCodeQueries().size();
ArrayList<ArrayList<Long>> ccIDs = new ArrayList();
for (int k = startQuery; k < (startQuery + queryCount); k++) {
ccIDs.add(elementids.get(k));
}
SVGUtilities.applyColorCodeToDocument(pv.getDrawing(), l, minimal, ccs.get(i), ccIDs, getConnection());
startQuery = startQuery + queryCount;
}
startQuery = 0;
for (int i = 0; i < ccs.size(); i++) {
int queryCount = ccs.get(i).getColorCodeQueries().size();
ArrayList<ArrayList<Long>> ccIDs = new ArrayList();
for (int k = startQuery; k < (startQuery + queryCount); k++) {
ccIDs.add(elementids.get(k));
}
SVGUtilities.applyColorCodeToDocument(pv.getDrawing(), l, minimal, ccs.get(i), ccIDs, con);
startQuery = startQuery + queryCount;
}
TranscoderInput transcoderInput = new TranscoderInput(minimal);
OutputStream out = null;
try {
String desiredName = "";
if (pvp.getSheetName() != null && pvp.getSheetName().length() > 0) {
desiredName = "//" + pv.getDrawing().getDrawingName() + " - " + pvp.getSheetName().replace("\\", "-").replace("/", "-");
} else {
desiredName = "//" + pv.getDrawing().getDrawingName() + " - " + pv.getPrintViewName();
}
int repeatCount = 0;
for (String s : usedFileNames) {
if (s.equals(desiredName)) {
repeatCount++;
}
}
usedFileNames.add(desiredName);
if (repeatCount > 0) {
desiredName = desiredName + "(" + (repeatCount++) + ")";
}
String fileName = absPath + desiredName + ".pdf";
fileNames.add(fileName);
out = new FileOutputStream(fileName);
} catch (Exception ex) {
logger.log(Level.SEVERE, null, ex);
}
TranscoderOutput transcoderOutput = new TranscoderOutput(out);
Transcoder finalPrintTranscoder = pdfTranscoder;
finalPrintTranscoder.addTranscodingHint(PDFTranscoder.KEY_ALLOW_EXTERNAL_RESOURCES, true);
// progress.setMainText("Exporting " + drawing.getDrawingName() + " to PDF (Page " + (p + 1) + " of " + pv.getPages().size() + ")");
try {
finalPrintTranscoder.transcode(transcoderInput, transcoderOutput);
////////
} catch (TranscoderException ex) {
// progress.stop();
ex.printStackTrace();
logger.log(Level.SEVERE, null, ex);
JOptionPane.showMessageDialog(null, "PDF export failed: " + ex.getMessage(), "Export Failed",
JOptionPane.INFORMATION_MESSAGE);
}
//Close all streams, set all elements to null, remove SVG elements from memory
try {
//minimal.removeChild(minimal.getDocumentElement());
SVGUtilities.removePrintString(minimal);
minimal.removeChild(minimal.getRootElement());
minimal.setDocumentURI(null);
minimal = null;
onLayers = null;
shadowLayers = null;
finalPrintTranscoder = null;
transcoderOutput.getOutputStream().flush();
transcoderOutput.getOutputStream().close();
out.close();
transcoderOutput = null;
transcoderInput = null;
pdfTranscoder = null;
} catch (IOException ex) {
Logger.getLogger(MainMAPSPanel.class
.getName()).log(Level.SEVERE, null, ex);
}
MemoryUtil.hardGC(2);
}
} catch (Exception e) {
e.printStackTrace();
}
if (combine && pv.getPages().size() > 1) {
try {
//progress.setMainText("Merging Pages into Single PDF File");
final PDFMergerUtility merger = new PDFMergerUtility();
for (String s : fileNames) {
merger.addSource(s);
}
merger.setDestinationFileName(absPath + "//" + pv.getDrawing().getDrawingName() + " - " + pv.getPrintViewName() + " - " + ".pdf");
merger.mergeDocuments(IOUtils.createTempFileOnlyStreamCache());
for (String s : fileNames) {
new File(s).delete();
}
//merger.setDestinationFileName(absPath + "\\" + pv.getDrawing().getDrawingName() + " - " + pv.getPrintViewName() + " - " + ".pdf");
//merger.mergeDocuments(IOUtils.createTempFileOnlyStreamCache());
for (String s : fileNames) {
new File(s).delete();
}
// return null;
} catch (Exception e) {
e.printStackTrace();
}
}
MemoryUtil.hardGC(2);
System.out.println(MAX_MEMORY + " " + MainFrame.mapsUser.getUserId());
System.out.println((runtime.maxMemory() - MAX_MEMORY) + " < " + (runtime.maxMemory() - (runtime.maxMemory() - runtime.freeMemory())));
//Stops printing and saves information to continue printing for next log-in,
//If suggested max memory (3.5gb - safe) is less than current available memory (3.5gb+ actively being used - 4gb is max for the JVM)
if(runtime.maxMemory() - MAX_MEMORY < runtime.maxMemory() - runtime.freeMemory()){
savePrintIndex(MainFrame.mapsUser.getUserId(), MainFrame.projectName, index, bp, absPath, combine);
System.out.println(MainFrame.projectName);
JOptionPane.showMessageDialog(null, "System has run out of Memory! \n" + "Application will restart, log back in for download to resume", "Out of Memory!",
JOptionPane.INFORMATION_MESSAGE);
try{
RestartApplication.restartApplication();
} catch(Exception e){
System.exit(0);
}
System.exit(0);
}
}
progress.stop();
return null;
}
@Override
protected void done() {
try {
get();
} catch (InterruptedException ex) {
logger.log(Level.SEVERE, null, ex);
} catch (ExecutionException ex) {
logger.log(Level.SEVERE, null, ex);
ex.printStackTrace();
} finally {
progress.stop();
}
progress.stop();
}
};
worker.execute();
progress.start();
//worker.execute();
//progress.start();
}