package jalview.gui; import static org.testng.Assert.assertEquals; 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.datamodel.AlignmentI; import jalview.datamodel.SequenceGroup; 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; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import junit.extensions.PA; public class FreeUpMemoryTest { private static final int ONE_MB = 1000 * 1000; /** * Configure (read-only) Jalview property settings for test */ @BeforeClass(alwaysRun = true) public void setUp() { 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 some annotation / Tree / PCA data) 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(35L); } /** * Requests garbage collection and then checks whether remaining memory in use * is less than the expected value (in Megabytes) * * @param expectedMax */ protected void checkUsedMemory(long expectedMax) { /* * request garbage collection and wait for it to run; * NB there is no guarantee when, or whether, it will do so * wait time depends on JRE/processor, generous allowance here */ System.gc(); waitFor(1500); /* * a second gc() call should not be necessary - but it is! * the test passes with it, and fails without it */ System.gc(); waitFor(1500); /* * check used memory is 'reasonably low' */ long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB; long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB; long usedMemory = availableMemory - freeMemory; /* * sanity check - fails if any frame was added after * closeAll_actionPerformed */ assertEquals(Desktop.instance.getAllFrames().length, 0); /* * 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 */ System.out.println("Used memory after gc = " + usedMemory + "MB"); 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); while (af.getViewport().isCalcInProgress()) { waitFor(200); } /* * open an Overview window */ af.overviewMenuItem_actionPerformed(null); assertNotNull(af.alignPanel.overviewPanel); /* * 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); /* * set a selection group - potential memory leak if it retains * a reference to the alignment */ SequenceGroup sg = new SequenceGroup(); sg.setStartRes(0); sg.setEndRes(100); AlignmentI al = af.viewport.getAlignment(); for (int i = 0; i < al.getHeight(); i++) { sg.addSequence(al.getSequenceAt(i), false); } af.viewport.setSelectionGroup(sg); /* * compute Tree and PCA (on all sequences, 100 columns) */ af.openTreePcaDialog(); CalculationChooser dialog = af.alignPanel.getCalculationDialog(); dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true)); dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false)); /* * wait until Tree and PCA have been computed */ while (af.viewport.getCurrentTree() == null && dialog.getPcaPanel().isWorking()) { waitFor(10); } /* * give Swing time to add the PCA panel (?!?) */ waitFor(100); } /** * Wait for waitMs miliseconds * * @param waitMs */ protected void waitFor(int waitMs) { try { Thread.sleep(waitMs); } catch (InterruptedException e) { } } /** * Generates an alignment and saves it in a temporary file, to be loaded by * Jalview. We use a peptide alignment (so Conservation and Quality are * calculated), which is wide enough to ensure Consensus, Conservation and * Occupancy have a significant memory footprint (if not removed from the * heap). * * @return * @throws IOException */ private File generateAlignment() throws IOException { File f = File.createTempFile("MemoryTest", "fa"); PrintStream ps = new PrintStream(f); AlignmentGenerator ag = new AlignmentGenerator(false, ps); int width = 100000; int height = 100; ag.generate(width, height, 0, 10, 15); return f; } }