DO NOT REPLY TO THIS EMAIL, BUT PLEASE POST YOUR BUGĀ· RELATED COMMENTS THROUGH THE WEB INTERFACE AVAILABLE AT <http://issues.apache.org/bugzilla/show_bug.cgi?id=41988>. ANY REPLY MADE TO THIS MESSAGE WILL NOT BE COLLECTED ANDĀ· INSERTED IN THE BUG DATABASE.
http://issues.apache.org/bugzilla/show_bug.cgi?id=41988 Summary: DOMScrambler torture test causes heap space OutOfMemoryError Product: Batik Version: 1.6 Platform: PC OS/Version: Linux Status: NEW Severity: major Priority: P2 Component: SVG DOM AssignedTo: [email protected] ReportedBy: [EMAIL PROTECTED] I've written a Batik torture test that loads an SVG document and makes zillions of DOM changes. Running this test on some (but not all) SVG documents that I've tried eventually leads to an OutOfMemoryException. Therefore, there must be a memory leak in Batik somewhere relating to (or triggered by) DOM manipulation. Example of a document that triggers the memory leak: http://svn.apache.org/repos/asf/xmlgraphics/batik/trunk/samples/tests/spec/linking/linkingViewBox.svg Example of a document that doesn't seem to trigger the memory leak: http://www.living-pages.de/de/projects/xop/samples/tiger.svg Here is the torture test and the little shell script I use to run it: -------------------------------------------------------------------- import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.util.ArrayList; import java.util.Random; import javax.swing.JFrame; import javax.swing.SwingUtilities; import org.apache.batik.bridge.UpdateManager; import org.apache.batik.bridge.UpdateManagerEvent; import org.apache.batik.bridge.UpdateManagerListener; import org.apache.batik.swing.JSVGCanvas; import org.apache.batik.swing.gvt.GVTTreeRendererEvent; import org.apache.batik.swing.gvt.GVTTreeRendererListener; import org.apache.batik.swing.svg.GVTTreeBuilderEvent; import org.apache.batik.swing.svg.GVTTreeBuilderListener; import org.apache.batik.swing.svg.SVGDocumentLoaderEvent; import org.apache.batik.swing.svg.SVGDocumentLoaderListener; import org.apache.batik.swing.svg.SVGLoadEventDispatcherEvent; import org.apache.batik.swing.svg.SVGLoadEventDispatcherListener; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.svg.SVGDefsElement; import org.w3c.dom.svg.SVGDocument; import org.w3c.dom.svg.SVGElement; import org.w3c.dom.svg.SVGGElement; public class DOMScrambler extends WindowAdapter implements SVGDocumentLoaderListener, GVTTreeBuilderListener, SVGLoadEventDispatcherListener, GVTTreeRendererListener, UpdateManagerListener { // Tweak these parameters to increase/decrease the torture public static final int SCRAMBLER_TIME = 100; // lower = more torture public static final int SCRAMBLER_NUM_CHANGES = 10; // higher = more torture private final Logger log = Logger.getLogger(getClass()); private final String url; private final JFrame frame; private final JSVGCanvas canvas; private final Random random = new Random(); private final ArrayList<SVGGElement> gnodes = new ArrayList<SVGGElement>(); private SVGDocument dom; public abstract class RepeatingThread extends Thread { public void run() { try { while (true) { execute(); } } catch (Throwable t) { t.printStackTrace(System.err); } } protected abstract void execute(); protected void delay(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } } public class MemoryReportingThread extends RepeatingThread { protected void execute() { Runtime runtime = Runtime.getRuntime(); runtime.gc(); System.out.println("MEMORY:"); System.out.println("\tFREE:\t" + runtime.freeMemory()); System.out.println("\tTOTAL:\t" + runtime.totalMemory()); System.out.println("\tMAX:\t" + runtime.maxMemory()); delay(10000); } } public class SleeperThread extends RepeatingThread { protected void execute() { delay(nextInt(500)); } } public class SwingNoise extends SleeperThread { protected void execute() { SwingUtilities.invokeLater(new Runnable() { public void run() { delay(nextInt(20)); } }); super.execute(); } } public class BatikNoise extends SleeperThread { protected void execute() { updateSVG(new Runnable() { public void run() { delay(nextInt(20)); } }); super.execute(); } } public class Scrambler extends SleeperThread { protected void execute() { updateSVG(new Runnable() { public void run() { for (int i = 0; i < nextInt(SCRAMBLER_NUM_CHANGES); i++) scramble(); } }); delay(nextInt(SCRAMBLER_TIME)); } protected void scramble() { tryagain: while (true) { SVGGElement g1 = gnodes.get(nextInt(gnodes.size())); SVGGElement g2 = gnodes.get(nextInt(gnodes.size())); NodeList kids1 = g1.getChildNodes(); NodeList kids2 = g2.getChildNodes(); if (kids1.getLength() == 0 && kids2.getLength() == 0) continue; Element parent1 = kids1.getLength() > 0 ? g1 : g2; Element parent2 = parent1 == g1 ? g2 : g1; kids1 = parent1.getChildNodes(); Node child = kids1.item(nextInt(kids1.getLength())); // Verify parent2 is not child or in child's subtree for (Node ancestor = parent2; ancestor != null; ancestor = ancestor.getParentNode()) { if (ancestor == child) continue tryagain; } // Swizzle child node //log.debug("moving child " + child + " from " + parent1 + " to " + parent2); parent1.removeChild(child); parent2.appendChild(child); break; } } } public DOMScrambler(String url) { this.url = url; frame = new JFrame("DOMScrambler: " + url.substring(url.lastIndexOf('/') + 1)); canvas = new JSVGCanvas(null, true, true); canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC); canvas.addSVGDocumentLoaderListener(this); canvas.addGVTTreeBuilderListener(this); canvas.addGVTTreeRendererListener(this); canvas.addSVGLoadEventDispatcherListener(this); canvas.addUpdateManagerListener(this); canvas.setFocusable(true); frame.addWindowListener(this); } public void go() { canvas.setURI(this.url); } public void cancel() { System.err.println("Something went wrong"); } protected void updateSVG(final Runnable rable) { UpdateManager updateManager = canvas.getUpdateManager(); updateManager.getUpdateRunnableQueue().invokeLater( new Runnable() { public void run() { try { rable.run(); } catch (Throwable t) { t.printStackTrace(System.err); System.exit(1); } } }); } protected int nextInt(int bound) { synchronized (random) { return random.nextInt(bound); } } protected void ready() { // Get SVG DOM this.dom = canvas.getSVGDocument(); // Locate all <g> nodes findGNodes(this.dom.getDocumentElement()); // Start threads new MemoryReportingThread().start(); new SleeperThread().start(); new SleeperThread().start(); new SleeperThread().start(); new SwingNoise().start(); new SwingNoise().start(); new BatikNoise().start(); new BatikNoise().start(); new Scrambler().start(); } private void findGNodes(Element elem) { if (elem instanceof SVGDefsElement) return; NodeList nodeList = elem.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node item = nodeList.item(i); if (item instanceof SVGGElement) gnodes.add((SVGGElement)item); if (item instanceof Element) findGNodes((Element)item); } } public void showFrame() { SwingUtilities.invokeLater(new Runnable() { public void run() { frame.getContentPane().add(canvas); frame.pack(); Dimension size = frame.getSize(); Point center = GraphicsEnvironment .getLocalGraphicsEnvironment().getCenterPoint(); int x = (int)(center.getX() - size.getWidth() / 2); int y = (int)(center.getY() - size.getHeight() / 2); frame.setLocation(new Point(x, y)); frame.setVisible(true); frame.toFront(); } }); } // SVGDocumentLoaderListener methods public void documentLoadingStarted(SVGDocumentLoaderEvent e) { log.debug("Document loading started"); } public void documentLoadingCompleted(SVGDocumentLoaderEvent e) { log.debug("Document loading completed"); } public void documentLoadingCancelled(SVGDocumentLoaderEvent e) { log.debug("Document loading canceled"); } public void documentLoadingFailed(SVGDocumentLoaderEvent e) { log.debug("Document loading failed: " + e); cancel(); } // GVTTreeBuilderListener methods public void gvtBuildStarted(GVTTreeBuilderEvent e) { log.debug("GVT build started"); } public void gvtBuildCompleted(GVTTreeBuilderEvent e) { log.debug("GVT build completed"); showFrame(); } public void gvtBuildCancelled(GVTTreeBuilderEvent e) { log.debug("GVT build canceled"); } public void gvtBuildFailed(GVTTreeBuilderEvent e) { log.debug("GVT build failed: " + e); cancel(); } // GVTTreeRendererListener methods public void gvtRenderingPrepare(GVTTreeRendererEvent e) { log.debug("GVT rendering preparing"); } public void gvtRenderingStarted(GVTTreeRendererEvent e) { log.debug("GVT rendering started"); } public void gvtRenderingCompleted(GVTTreeRendererEvent e) { log.debug("GVT rendering complete"); ready(); } public void gvtRenderingCancelled(GVTTreeRendererEvent e) { log.debug("GVT rendering canceled"); } public void gvtRenderingFailed(GVTTreeRendererEvent e) { log.debug("GVT rendering failed: " + e); cancel(); } // SVGLoadEventDispatcherListener methods public void svgLoadEventDispatchCancelled(SVGLoadEventDispatcherEvent e) { log.debug("Load event dispatch cancelled"); } public void svgLoadEventDispatchCompleted(SVGLoadEventDispatcherEvent e) { log.debug("Load event dispatch completed"); } public void svgLoadEventDispatchFailed(SVGLoadEventDispatcherEvent e) { log.debug("Load event dispatch failed: " + e); } public void svgLoadEventDispatchStarted(SVGLoadEventDispatcherEvent e) { log.debug("Load event dispatch started"); } // UpdateManagerListener public void managerStarted(UpdateManagerEvent e) { log.debug("Update manager started"); } public void managerSuspended(UpdateManagerEvent e) { log.debug("Update manager suspended"); } public void managerResumed(UpdateManagerEvent e) { log.debug("Update manager resumed"); } public void managerStopped(UpdateManagerEvent e) { log.debug("Update manager stopped"); } public void updateStarted(UpdateManagerEvent e) { //log.debug("Update manager started"); } public void updateCompleted(UpdateManagerEvent e) { //log.debug("Update manager completed"); } public void updateFailed(UpdateManagerEvent e) { log.debug("Update manager failed: " + e); } // WindowListener methods public void windowClosing(WindowEvent e) { System.exit(0); } public static void main(String[] args) throws Exception { ConsoleAppender consoleAppender = new ConsoleAppender( new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN), ConsoleAppender.SYSTEM_ERR); consoleAppender.setThreshold(Level.DEBUG); BasicConfigurator.configure(consoleAppender); if (args.length != 1) { System.err.println("Usage: java DOMScrambler <file | URL>"); System.exit(1); } File file = new File(args[0]); if (file.exists()) args[0] = file.toURL().toString(); new DOMScrambler(args[0]).go(); } } -------------------------------------------------------------------- #!/bin/sh LIBS=`build-classpath batik jakarta-commons-collections jakarta-commons-lang jakarta-commons-logging log4j spring xerces-j2 xml-commons-apis` set -e mkdir -p classes javac -d classes -classpath "${LIBS}" DOMScrambler.java java -Xmx40m -classpath classes:"${LIBS}" DOMScrambler ${1+"$@"} -------------------------------------------------------------------- -- Configure bugmail: http://issues.apache.org/bugzilla/userprefs.cgi?tab=email ------- You are receiving this mail because: ------- You are the assignee for the bug, or are watching the assignee. --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
