2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import static org.testng.Assert.assertNotNull;
24 import static org.testng.Assert.assertTrue;
27 import java.awt.event.MouseEvent;
29 import java.io.IOException;
30 import java.io.PrintStream;
32 import org.testng.annotations.BeforeClass;
33 import org.testng.annotations.Test;
35 import jalview.analysis.AlignmentGenerator;
36 import jalview.bin.Cache;
37 import jalview.bin.Jalview;
38 import jalview.datamodel.AlignmentI;
39 import jalview.datamodel.SequenceGroup;
40 import jalview.io.DataSourceType;
41 import jalview.io.FileLoader;
42 import junit.extensions.PA;
45 * Provides a simple test that memory is released when all windows are closed.
47 * <li>generates a reasonably large alignment and loads it</li>
48 * <li>performs various operations on the alignment</li>
49 * <li>closes all windows</li>
50 * <li>requests garbage collection</li>
51 * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
54 * If the test fails, this means that reference(s) to large object(s) have
55 * failed to be garbage collected. In this case:
57 * <li>set a breakpoint just before the test assertion in
58 * {@code checkUsedMemory}</li>
59 * <li>if the test fails intermittently, make this breakpoint conditional on
60 * {@code usedMemory > expectedMax}</li>
61 * <li>run the test to this point (and check that it is about to fail i.e.
62 * {@code usedMemory > expectedMax})</li>
63 * <li>use <a href="https://visualvm.github.io/">visualvm</a> to obtain a heap
64 * dump from the suspended process (and kill the test or let it fail)</li>
65 * <li>inspect the heap dump using visualvm for large objects and their
69 * <li>Perform GC from the Monitor view in visualvm before requesting the heap
70 * dump - test failure might be simply a delay to GC</li>
71 * <li>View 'Objects' and filter classes to {@code jalview}. Sort columns by
72 * Count, or Size, and look for anything suspicious. For example, if the object
73 * count for {@code Sequence} is non-zero (it shouldn't be), pick any instance,
74 * and follow the chain of {@code references} to find which class(es) still hold
75 * references to sequence objects</li>
76 * <li>If this chain is impracticably long, re-run the test with a smaller
77 * alignment (set width=100, height=10 in {@code generateAlignment()}), to
78 * capture a heap which is qualitatively the same, but much smaller, so easier
79 * to analyse; note this requires an unconditional breakpoint</li>
83 * <h2>Fixing memory leaks</h2>
85 * Experience shows that often a reference is retained (directly or indirectly)
86 * by a Swing (or related) component (for example a {@code MouseListener} or
87 * {@code ActionListener}). There are two possible approaches to fixing:
89 * <li>Purist: ensure that all listeners and similar objects are removed when no
90 * longer needed. May be difficult, to achieve and to maintain as code
92 * <li>Pragmatic: null references to potentially large objects from Jalview
93 * application classes when no longer needed, typically when a panel is closed.
94 * This ensures that even if the JVM keeps a reference to a panel or viewport,
95 * it does not retain a large heap footprint. This is the approach taken in, for
96 * example, {@code AlignmentPanel.closePanel()} and
97 * {@code AnnotationPanel.dispose()}.</li>
98 * <li>Adjust code if necessary; for example an {@code ActionListener} should
99 * act on {@code av.getAlignment()} and not directly on {@code alignment}, as
100 * the latter pattern could leave persistent references to the alignment</li>
102 * Add code to 'null unused large object references' until the test passes. For
103 * a final sanity check, capture the heap dump for a passing test, and satisfy
104 * yourself that only 'small' or 'harmless' {@code jalview} object instances
105 * (such as enums or singletons) are left in the heap.
107 public class FreeUpMemoryTest
109 private static final int ONE_MB = 1000 * 1000;
112 * maximum retained heap usage (in MB) for a passing test
114 private static int MAX_RESIDUAL_HEAP = 45;
116 * Configure (read-only) Jalview property settings for test
118 @BeforeClass(alwaysRun = true)
123 { "-nonews", "-props", "test/jalview/testProps.jvprops" });
124 String True = Boolean.TRUE.toString();
125 Cache.setPropertyNoSave("SHOW_ANNOTATIONS", True);
126 Cache.setPropertyNoSave("SHOW_QUALITY", True);
127 Cache.setPropertyNoSave("SHOW_CONSERVATION", True);
128 Cache.setPropertyNoSave("SHOW_OCCUPANCY", True);
129 Cache.setPropertyNoSave("SHOW_IDENTITY", True);
133 * A simple test that memory is released when all windows are closed.
135 * <li>generates a reasonably large alignment and loads it</li>
136 * <li>performs various operations on the alignment</li>
137 * <li>closes all windows</li>
138 * <li>requests garbage collection</li>
139 * <li>asserts that the remaining memory footprint (heap usage) is 'not large'
142 * If the test fails, this suggests that a reference to some large object
143 * (perhaps the alignment data, or some annotation / Tree / PCA data) has
144 * failed to be garbage collected. If this is the case, the heap will need to
145 * be inspected manually (suggest using jvisualvm) in order to track down
146 * where large objects are still referenced. The code (for example
147 * AlignmentViewport.dispose()) should then be updated to ensure references to
148 * large objects are set to null when they are no longer required.
150 * @throws IOException
152 @Test(groups = "Memory")
153 public void testFreeMemoryOnClose() throws IOException
155 File f = generateAlignment();
158 long expectedMin = MAX_RESIDUAL_HEAP;
159 long usedMemoryAtStart=getUsedMemory();
160 if (usedMemoryAtStart>expectedMin)
162 System.err.println("used memory before test is "+usedMemoryAtStart+" > "+expectedMin+"MB .. adjusting minimum.");
163 expectedMin = usedMemoryAtStart;
167 Desktop.getInstance().closeAll_actionPerformed(null);
169 checkUsedMemory(expectedMin);
173 * Returns the current total used memory (available memory - free memory),
174 * rounded down to the nearest MB
178 private static int getUsedMemory()
180 long availableMemory = Runtime.getRuntime().totalMemory();
181 long freeMemory = Runtime.getRuntime().freeMemory();
182 long usedMemory = availableMemory - freeMemory;
183 return (int) (usedMemory / ONE_MB);
186 * Requests garbage collection and then checks whether remaining memory in use
187 * is less than the expected value (in Megabytes)
191 protected void checkUsedMemory(long expectedMax)
194 * request garbage collection and wait for it to run (up to 3 times);
195 * NB there is no guarantee when, or whether, it will do so
197 long usedMemory = 0L;
198 Long minUsedMemory = null;
205 usedMemory = getUsedMemory();
206 if (minUsedMemory == null || usedMemory < minUsedMemory)
208 minUsedMemory = usedMemory;
210 if (usedMemory < expectedMax)
217 * if this assertion fails (reproducibly!)
218 * - set a breakpoint here, conditional on (usedMemory > expectedMax)
219 * - run VisualVM to inspect the heap usage, and run GC from VisualVM to check
220 * it is not simply delayed garbage collection causing the test failure
221 * - take a heap dump and identify large objects in the heap and their referers
222 * - fix code as necessary to null the references on close
224 System.out.println("(Minimum) Used memory after " + gcCount
225 + " call(s) to gc() = " + minUsedMemory + "MB (should be <="
226 + expectedMax + ")");
227 assertTrue(usedMemory <= expectedMax, String.format(
228 "Used memory %d should be less than %d (Recommend running test manually to verify)",
229 usedMemory, expectedMax));
233 * Loads an alignment from file and exercises various operations in Jalview
237 protected void doStuffInJalview(File f)
240 * load alignment, wait for consensus and other threads to complete
242 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(),
243 DataSourceType.FILE);
244 while (af.getViewport().isCalcInProgress())
250 * open an Overview window
252 af.overviewMenuItem_actionPerformed(null);
253 assertNotNull(af.alignPanel.overviewPanel);
256 * exercise the pop-up menu in the Overview Panel (JAL-2864)
258 Object[] args = new Object[] {
259 new MouseEvent(af, 0, 0, 0, 0, 0, 1, true) };
260 PA.invokeMethod(af.alignPanel.overviewPanel,
261 "showPopupMenu(java.awt.event.MouseEvent)", args);
264 * set a selection group - potential memory leak if it retains
265 * a reference to the alignment
267 SequenceGroup sg = new SequenceGroup();
270 AlignmentI al = af.viewport.getAlignment();
271 for (int i = 0; i < al.getHeight(); i++)
273 sg.addSequence(al.getSequenceAt(i), false);
275 af.viewport.setSelectionGroup(sg);
278 * compute Tree and PCA (on all sequences, 100 columns)
280 af.openTreePcaDialog();
281 CalculationChooser dialog = af.alignPanel.getCalculationDialog();
282 dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true));
283 dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false));
286 * wait until Tree and PCA have been computed
288 while (af.viewport.getCurrentTree() == null
289 || dialog.getPcaPanel().isWorking())
295 * give Swing time to add the PCA panel (?!?)
301 * Wait for waitMs miliseconds
305 protected void waitFor(int waitMs)
309 Thread.sleep(waitMs);
310 } catch (InterruptedException e)
316 * Generates an alignment and saves it in a temporary file, to be loaded by
317 * Jalview. We use a peptide alignment (so Conservation and Quality are
318 * calculated), which is wide enough to ensure Consensus, Conservation and
319 * Occupancy have a significant memory footprint (if not removed from the
323 * @throws IOException
325 private File generateAlignment() throws IOException
327 File f = File.createTempFile("MemoryTest", "fa");
328 PrintStream ps = new PrintStream(f);
329 AlignmentGenerator ag = new AlignmentGenerator(false, ps);
332 ag.generate(width, height, 0, 10, 15);