X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=test%2Fjalview%2Fgui%2FFreeUpMemoryTest.java;h=56f86525fdca024bf2b802996d058bfd7bef37a2;hb=4a3def9f59cefe629c9a33d87483283aee085928;hp=9fc0678fdb20ee8af214292447d505351a1c5b01;hpb=8b21c13f49da87d7f13ec5efa3d7b4054aa3fd5f;p=jalview.git diff --git a/test/jalview/gui/FreeUpMemoryTest.java b/test/jalview/gui/FreeUpMemoryTest.java index 9fc0678..56f8652 100644 --- a/test/jalview/gui/FreeUpMemoryTest.java +++ b/test/jalview/gui/FreeUpMemoryTest.java @@ -1,13 +1,29 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.gui; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -import jalview.analysis.AlignmentGenerator; -import jalview.bin.Cache; -import jalview.bin.Jalview; -import jalview.io.DataSourceType; -import jalview.io.FileLoader; - +import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.io.PrintStream; @@ -15,123 +31,266 @@ import java.io.PrintStream; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import jalview.analysis.AlignmentGenerator; +import jalview.bin.Cache; +import jalview.bin.Jalview; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceGroup; +import jalview.io.DataSourceType; +import jalview.io.FileLoader; +import junit.extensions.PA; + +/** + * Provides a simple test that memory is released when all windows are closed. + * + * If the test fails, this means that reference(s) to large object(s) have + * failed to be garbage collected. In this case: + * + *

+ *

Fixing memory leaks

+ *

+ * Experience shows that often a reference is retained (directly or indirectly) + * by a Swing (or related) component (for example a {@code MouseListener} or + * {@code ActionListener}). There are two possible approaches to fixing: + *

+ * Add code to 'null unused large object references' until the test passes. For + * a final sanity check, capture the heap dump for a passing test, and satisfy + * yourself that only 'small' or 'harmless' {@code jalview} object instances + * (such as enums or singletons) are left in the heap. + */ public class FreeUpMemoryTest { private static final int ONE_MB = 1000 * 1000; + /* + * maximum retained heap usage (in MB) for a passing test + */ + private static int MAX_RESIDUAL_HEAP = 45; + /** * Configure (read-only) Jalview property settings for test */ @BeforeClass(alwaysRun = true) public void setUp() { - Jalview.main(new String[] { "-nonews", "-props", - "test/jalview/testProps.jvprops" }); - Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", - Boolean.TRUE.toString()); - Cache.applicationProperties.setProperty("SHOW_QUALITY", - Boolean.TRUE.toString()); - Cache.applicationProperties.setProperty("SHOW_CONSERVATION", - Boolean.TRUE.toString()); - Cache.applicationProperties.setProperty("SHOW_OCCUPANCY", - Boolean.TRUE.toString()); - Cache.applicationProperties.setProperty("SHOW_IDENTITY", - Boolean.TRUE.toString()); + Jalview.main( + new String[] + { "-nonews", "-props", "test/jalview/testProps.jvprops" }); + String True = Boolean.TRUE.toString(); + Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", True); + Cache.applicationProperties.setProperty("SHOW_QUALITY", True); + Cache.applicationProperties.setProperty("SHOW_CONSERVATION", True); + Cache.applicationProperties.setProperty("SHOW_OCCUPANCY", True); + Cache.applicationProperties.setProperty("SHOW_IDENTITY", True); } - /** - * A simple test that memory is released when all windows are closed. - * - * If the test fails, this suggests that a reference to some large object - * (perhaps the alignment data, or consensus profile) has failed to be garbage - * collected. If this is the case, the heap will need to be inspected manually - * (suggest using jvisualvm) in order to track down where large objects are - * still referenced. The code (for example AlignmentViewport.dispose()) should - * then be updated to ensure references to large objects are set to null when - * they are no longer required. - * - * @throws IOException - */ @Test(groups = "Memory") public void testFreeMemoryOnClose() throws IOException { File f = generateAlignment(); f.deleteOnExit(); + doStuffInJalview(f); + + Desktop.instance.closeAll_actionPerformed(null); + + checkUsedMemory(MAX_RESIDUAL_HEAP); + } + + /** + * Returns the current total used memory (available memory - free memory), + * rounded down to the nearest MB + * + * @return + */ + private static int getUsedMemory() + { + long availableMemory = Runtime.getRuntime().totalMemory(); + long freeMemory = Runtime.getRuntime().freeMemory(); + long usedMemory = availableMemory - freeMemory; + + return (int) (usedMemory / ONE_MB); + } + + /** + * Requests garbage collection and then checks whether remaining memory in use + * is less than the expected value (in Megabytes) + * + * @param expectedMax + */ + protected void checkUsedMemory(int expectedMax) + { + /* + * request garbage collection and wait for it to run (up to 3 times); + * NB there is no guarantee when, or whether, it will do so + */ + long usedMemory = 0L; + Long minUsedMemory = null; + int gcCount = 0; + while (gcCount < 3) + { + gcCount++; + System.gc(); + waitFor(1500); + usedMemory = getUsedMemory(); + if (minUsedMemory == null || usedMemory < minUsedMemory) + { + minUsedMemory = usedMemory; + } + if (usedMemory < expectedMax) + { + break; + } + } + + /* + * if this assertion fails (reproducibly!) + * - set a breakpoint here, conditional on (usedMemory > expectedMax) + * - run VisualVM to inspect the heap usage, and run GC from VisualVM to check + * it is not simply delayed garbage collection causing the test failure + * - take a heap dump and identify large objects in the heap and their referers + * - fix code as necessary to null the references on close + */ + System.out.println("(Minimum) Used memory after " + gcCount + + " call(s) to gc() = " + minUsedMemory + "MB (should be <=" + + expectedMax + ")"); + assertTrue(usedMemory <= expectedMax, String.format( + "Used memory %d should be less than %d (Recommend running test manually to verify)", + usedMemory, expectedMax)); + } + + /** + * Loads an alignment from file and exercises various operations in Jalview + * + * @param f + */ + protected void doStuffInJalview(File f) + { /* * load alignment, wait for consensus and other threads to complete */ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(), DataSourceType.FILE); - waitForThreads(af.getViewport()); + while (af.getViewport().isCalcInProgress()) + { + waitFor(200); + } + + /* + * open an Overview window + */ + af.overviewMenuItem_actionPerformed(null); + assertNotNull(af.alignPanel.overviewPanel); - af.closeMenuItem_actionPerformed(true); + /* + * exercise the pop-up menu in the Overview Panel (JAL-2864) + */ + Object[] args = new Object[] { + new MouseEvent(af, 0, 0, 0, 0, 0, 1, true) }; + PA.invokeMethod(af.alignPanel.overviewPanel, + "showPopupMenu(java.awt.event.MouseEvent)", args); /* - * request garbage collection and wait briefly for it to run; - * NB there is no guarantee when, or whether, it will do so + * set a selection group - potential memory leak if it retains + * a reference to the alignment */ - System.gc(); - synchronized (this) + SequenceGroup sg = new SequenceGroup(); + sg.setStartRes(0); + sg.setEndRes(100); + AlignmentI al = af.viewport.getAlignment(); + for (int i = 0; i < al.getHeight(); i++) { - try - { - wait(10); - } catch (InterruptedException e) - { - } + sg.addSequence(al.getSequenceAt(i), false); } + af.viewport.setSelectionGroup(sg); /* - * a second gc() call should not be necessary - but it is! - * the test passes with it, and fails without it + * compute Tree and PCA (on all sequences, 100 columns) */ - System.gc(); + af.openTreePcaDialog(); + CalculationChooser dialog = af.alignPanel.getCalculationDialog(); + dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true)); + try + { + dialog.createTree("BLOSUM62", dialog.getSimilarityParameters(false)); + } catch (IOException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } /* - * check used memory is 'reasonably low' + * wait until Tree and PCA have been computed */ - long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB; - long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB; - long usedMemory = availableMemory - freeMemory; - System.out.println("Memory in use after close all windows: " - + usedMemory + "MB"); + while (af.viewport.getCurrentTree() == null + || dialog.getPcaPanel().isWorking()) + { + waitFor(10); + } /* - * if this assertion fails - * - set a breakpoint here - * - run jvisualvm to inspect a heap dump of Jalview - * - identify large objects in the heap and their referers - * - fix code as necessary to null the references on close + * give Swing time to add the PCA panel (?!?) */ - long expectedMax = 30L; // typically reports around 25 - assertTrue(usedMemory < expectedMax, String.format( - "Used memory %d should be less than %d", usedMemory, - expectedMax)); + waitFor(100); } /** - * wait for consensus etc thread to complete + * Wait for waitMs miliseconds * - * @param av + * @param waitMs */ - protected void waitForThreads(AlignViewport av) + protected void waitFor(int waitMs) { - while (av.isCalcInProgress()) + try + { + Thread.sleep(waitMs); + } catch (InterruptedException e) { - try - { - Thread.sleep(200); - } catch (Exception x) - { - } } } @@ -153,6 +312,7 @@ public class FreeUpMemoryTest int width = 100000; int height = 100; ag.generate(width, height, 0, 10, 15); + ps.close(); return f; } }