3 import static org.testng.Assert.assertEquals;
4 import static org.testng.Assert.assertNotNull;
5 import static org.testng.Assert.assertTrue;
7 import jalview.analysis.AlignmentGenerator;
8 import jalview.bin.Cache;
9 import jalview.bin.Jalview;
10 import jalview.datamodel.AlignmentI;
11 import jalview.datamodel.SequenceGroup;
12 import jalview.io.DataSourceType;
13 import jalview.io.FileLoader;
15 import java.awt.event.MouseEvent;
17 import java.io.IOException;
18 import java.io.PrintStream;
20 import org.testng.annotations.BeforeClass;
21 import org.testng.annotations.Test;
23 import junit.extensions.PA;
26 * Provides a simple test that memory is released when all windows are closed.
28 * <li>generates a reasonably large alignment and loads it</li>
29 * <li>performs various operations on the alignment</li>
30 * <li>closes all windows</li>
31 * <li>requests garbage collection</li>
32 * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
35 * If the test fails, this means that reference(s) to large object(s) have
36 * failed to be garbage collected. In this case:
38 * <li>set a breakpoint just before the test assertion in
39 * {@code checkUsedMemory}</li>
40 * <li>if the test fails intermittently, make this breakpoint conditional on
41 * {@code usedMemory > expectedMax}</li>
42 * <li>run the test to this point (and check that it is about to fail i.e.
43 * {@code usedMemory > expectedMax})</li>
44 * <li>use <a href="https://visualvm.github.io/">visualvm</a> to obtain a heap
45 * dump from the suspended process (and kill the test or let it fail)</li>
46 * <li>inspect the heap dump using visualvm for large objects and their
50 * <li>Perform GC from the Monitor view in visualvm before requesting the heap
52 * <li>View 'Objects' and filter classes to {@code jalview}. Sort columns by
53 * Count, or Size, and look for anything suspicious. For example, if the object
54 * count for {@code Sequence} is non-zero (it shouldn't be), pick any instance,
55 * and follow the chain of {@code references} to find which class(es) still hold
56 * references to sequence objects</li>
57 * <li>If this chain is impracticably long, re-run the test with a smaller
58 * alignment (set width=100, height=10 in {@code generateAlignment()}), to
59 * capture a heap which is qualitatively the same, but much smaller, so easier
60 * to analyse; note this requires an unconditional breakpoint</li>
64 * <h2>Fixing memory leaks</h2>
66 * Experience shows that often a reference is retained (directly or indirectly)
67 * by a Swing (or related) component (for example a {@code MouseListener} or
68 * {@code ActionListener}). There are two possible approaches to fixing:
70 * <li>Purist: ensure that all listeners and similar objects are removed when no
71 * longer needed. May be difficult, to achieve and to maintain as code
73 * <li>Pragmatic: null references to potentially large objects from Jalview
74 * application classes when no longer needed, typically when a panel is closed.
75 * This ensures that even if the JVM keeps a reference to a panel or viewport,
76 * it does not retain a large heap footprint. This is the approach taken in, for
77 * example, {@code AlignmentPanel.closePanel()} and
78 * {@code AnnotationPanel.dispose()}.</li>
79 * <li>Adjust code if necessary; for example an {@code ActionListener} should
80 * act on {@code av.getAlignment()} and not directly on {@code alignment}, as
81 * the latter pattern could leave persistent references to the alignment</li>
83 * Add code to 'null unused large object references' until the test passes. For
84 * a final sanity check, capture the heap dump for a passing test, and satisfy
85 * yourself that only 'small' or 'harmless' {@code jalview} object instances
86 * (such as enums or singletons) are left in the heap.
88 public class FreeUpMemoryTest
90 private static final int ONE_MB = 1000 * 1000;
93 * Configure (read-only) Jalview property settings for test
95 @BeforeClass(alwaysRun = true)
100 { "-nonews", "-props", "test/jalview/testProps.jvprops" });
101 String True = Boolean.TRUE.toString();
102 Cache.applicationProperties.setProperty("SHOW_ANNOTATIONS", True);
103 Cache.applicationProperties.setProperty("SHOW_QUALITY", True);
104 Cache.applicationProperties.setProperty("SHOW_CONSERVATION", True);
105 Cache.applicationProperties.setProperty("SHOW_OCCUPANCY", True);
106 Cache.applicationProperties.setProperty("SHOW_IDENTITY", True);
109 @Test(groups = "Memory")
110 public void testFreeMemoryOnClose() throws IOException
112 long expectedMin = 37L;
114 File f = generateAlignment();
119 Desktop.instance.closeAll_actionPerformed(null);
121 checkUsedMemory(expectedMin);
124 private static long getUsedMemory()
126 long availableMemory = Runtime.getRuntime().totalMemory() / ONE_MB;
127 long freeMemory = Runtime.getRuntime().freeMemory() / ONE_MB;
128 long usedMemory = availableMemory - freeMemory;
133 * Requests garbage collection and then checks whether remaining memory in use
134 * is less than the expected value (in Megabytes)
138 protected void checkUsedMemory(long expectedMax)
141 * request garbage collection and wait for it to run;
142 * NB there is no guarantee when, or whether, it will do so
143 * wait time depends on JRE/processor, generous allowance here
149 * a second gc() call should not be necessary - but it is!
150 * the test passes with it, and fails without it
156 * check used memory is 'reasonably low'
158 long usedMemory = getUsedMemory();
160 * sanity check - fails if any frame was added after
161 * closeAll_actionPerformed
163 assertEquals(Desktop.instance.getAllFrames().length, 0);
166 * if this assertion fails
167 * - set a breakpoint here
168 * - run jvisualvm to inspect a heap dump of Jalview
169 * - identify large objects in the heap and their referers
170 * - fix code as necessary to null the references on close
172 System.out.println("Used memory after gc = " + usedMemory
173 + "MB (should be <" + expectedMax + ")");
174 assertTrue(usedMemory < expectedMax, String.format(
175 "Used memory %d should be less than %d (Recommend running test manually to verify)",
176 usedMemory, expectedMax));
180 * Loads an alignment from file and exercises various operations in Jalview
184 protected void doStuffInJalview(File f)
187 * load alignment, wait for consensus and other threads to complete
189 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(),
190 DataSourceType.FILE);
191 while (af.getViewport().isCalcInProgress())
197 * open an Overview window
199 af.overviewMenuItem_actionPerformed(null);
200 assertNotNull(af.alignPanel.overviewPanel);
203 * exercise the pop-up menu in the Overview Panel (JAL-2864)
205 Object[] args = new Object[] {
206 new MouseEvent(af, 0, 0, 0, 0, 0, 1, true) };
207 PA.invokeMethod(af.alignPanel.overviewPanel,
208 "showPopupMenu(java.awt.event.MouseEvent)", args);
211 * set a selection group - potential memory leak if it retains
212 * a reference to the alignment
214 SequenceGroup sg = new SequenceGroup();
217 AlignmentI al = af.viewport.getAlignment();
218 for (int i = 0; i < al.getHeight(); i++)
220 sg.addSequence(al.getSequenceAt(i), false);
222 af.viewport.setSelectionGroup(sg);
225 * compute Tree and PCA (on all sequences, 100 columns)
227 af.openTreePcaDialog();
228 CalculationChooser dialog = af.alignPanel.getCalculationDialog();
229 dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true));
230 dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false));
233 * wait until Tree and PCA have been computed
235 while (af.viewport.getCurrentTree() == null
236 || dialog.getPcaPanel().isWorking())
242 * give Swing time to add the PCA panel (?!?)
248 * Wait for waitMs miliseconds
252 protected void waitFor(int waitMs)
256 Thread.sleep(waitMs);
257 } catch (InterruptedException e)
263 * Generates an alignment and saves it in a temporary file, to be loaded by
264 * Jalview. We use a peptide alignment (so Conservation and Quality are
265 * calculated), which is wide enough to ensure Consensus, Conservation and
266 * Occupancy have a significant memory footprint (if not removed from the
270 * @throws IOException
272 private File generateAlignment() throws IOException
274 File f = File.createTempFile("MemoryTest", "fa");
275 PrintStream ps = new PrintStream(f);
276 AlignmentGenerator ag = new AlignmentGenerator(false, ps);
279 ag.generate(width, height, 0, 10, 15);