Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.gui

File FreeUpMemoryTest.java

 

Code metrics

16
67
7
1
337
140
18
0.27
9.57
7
2.57

Classes

Class Line # Actions
FreeUpMemoryTest 107 67 18
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
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.
11    *
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.
16    *
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.
20    */
21    package jalview.gui;
22   
23    import static org.testng.Assert.assertNotNull;
24    import static org.testng.Assert.assertTrue;
25   
26   
27    import java.awt.event.MouseEvent;
28    import java.io.File;
29    import java.io.IOException;
30    import java.io.PrintStream;
31   
32    import org.testng.annotations.BeforeClass;
33    import org.testng.annotations.Test;
34   
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;
43   
44    /**
45    * Provides a simple test that memory is released when all windows are closed.
46    * <ul>
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'
52    * </li>
53    * </ul>
54    * If the test fails, this means that reference(s) to large object(s) have
55    * failed to be garbage collected. In this case:
56    * <ul>
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
66    * referers</li>
67    * <li>Tips:</li>
68    * <ul>
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>
80    * </ul>
81    * </ul>
82    * <p>
83    * <h2>Fixing memory leaks</h2>
84    * <p>
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:
88    * <ul>
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
91    * changes.</li>
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>
101    * </ul>
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.
106    */
 
107    public class FreeUpMemoryTest
108    {
109    private static final int ONE_MB = 1000 * 1000;
110   
111    /*
112    * maximum retained heap usage (in MB) for a passing test
113    */
114    private static int MAX_RESIDUAL_HEAP = 45;
115    /**
116    * Configure (read-only) Jalview property settings for test
117    */
 
118  0 toggle @BeforeClass(alwaysRun = true)
119    public void setUp()
120    {
121  0 Jalview.main(
122    new String[]
123    { "--nonews", "--props", "test/jalview/testProps.jvprops" });
124  0 String True = Boolean.TRUE.toString();
125  0 Cache.setPropertyNoSave("SHOW_ANNOTATIONS", True);
126  0 Cache.setPropertyNoSave("SHOW_QUALITY", True);
127  0 Cache.setPropertyNoSave("SHOW_CONSERVATION", True);
128  0 Cache.setPropertyNoSave("SHOW_OCCUPANCY", True);
129  0 Cache.setPropertyNoSave("SHOW_IDENTITY", True);
130    }
131   
132    /**
133    * A simple test that memory is released when all windows are closed.
134    * <ul>
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'
140    * </li>
141    * </ul>
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.
149    *
150    * @throws IOException
151    */
 
152  0 toggle @Test(groups = "Memory")
153    public void testFreeMemoryOnClose() throws IOException
154    {
155  0 File f = generateAlignment();
156  0 f.deleteOnExit();
157   
158  0 long expectedMin = MAX_RESIDUAL_HEAP;
159  0 long usedMemoryAtStart=getUsedMemory();
160  0 if (usedMemoryAtStart>expectedMin)
161    {
162  0 System.err.println("used memory before test is "+usedMemoryAtStart+" > "+expectedMin+"MB .. adjusting minimum.");
163  0 expectedMin = usedMemoryAtStart;
164    }
165  0 doStuffInJalview(f);
166   
167  0 if (Desktop.getInstance() != null)
168  0 Desktop.getInstance().closeAll_actionPerformed(null);
169   
170  0 checkUsedMemory(expectedMin);
171    }
172   
173    /**
174    * Returns the current total used memory (available memory - free memory),
175    * rounded down to the nearest MB
176    *
177    * @return
178    */
 
179  0 toggle private static int getUsedMemory()
180    {
181  0 long availableMemory = Runtime.getRuntime().totalMemory();
182  0 long freeMemory = Runtime.getRuntime().freeMemory();
183  0 long usedMemory = availableMemory - freeMemory;
184  0 return (int) (usedMemory / ONE_MB);
185    }
186    /**
187    * Requests garbage collection and then checks whether remaining memory in use
188    * is less than the expected value (in Megabytes)
189    *
190    * @param expectedMax
191    */
 
192  0 toggle protected void checkUsedMemory(long expectedMax)
193    {
194    /*
195    * request garbage collection and wait for it to run (up to 3 times);
196    * NB there is no guarantee when, or whether, it will do so
197    */
198  0 long usedMemory = 0L;
199  0 Long minUsedMemory = null;
200  0 int gcCount = 0;
201  0 while (gcCount < 3)
202    {
203  0 gcCount++;
204  0 System.gc();
205  0 waitFor(1500);
206  0 usedMemory = getUsedMemory();
207  0 if (minUsedMemory == null || usedMemory < minUsedMemory)
208    {
209  0 minUsedMemory = usedMemory;
210    }
211  0 if (usedMemory < expectedMax)
212    {
213  0 break;
214    }
215    }
216   
217    /*
218    * if this assertion fails (reproducibly!)
219    * - set a breakpoint here, conditional on (usedMemory > expectedMax)
220    * - run VisualVM to inspect the heap usage, and run GC from VisualVM to check
221    * it is not simply delayed garbage collection causing the test failure
222    * - take a heap dump and identify large objects in the heap and their referers
223    * - fix code as necessary to null the references on close
224    */
225  0 System.out.println("(Minimum) Used memory after " + gcCount
226    + " call(s) to gc() = " + minUsedMemory + "MB (should be <="
227    + expectedMax + ")");
228  0 assertTrue(usedMemory <= expectedMax, String.format(
229    "Used memory %d should be less than %d (Recommend running test manually to verify)",
230    usedMemory, expectedMax));
231    }
232   
233    /**
234    * Loads an alignment from file and exercises various operations in Jalview
235    *
236    * @param f
237    */
 
238  0 toggle protected void doStuffInJalview(File f)
239    {
240    /*
241    * load alignment, wait for consensus and other threads to complete
242    */
243  0 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(f.getPath(),
244    DataSourceType.FILE);
245  0 while (af.getViewport().isCalcInProgress())
246    {
247  0 waitFor(200);
248    }
249   
250    /*
251    * open an Overview window
252    */
253  0 af.overviewMenuItem_actionPerformed(null);
254  0 assertNotNull(af.alignPanel.overviewPanel);
255   
256    /*
257    * exercise the pop-up menu in the Overview Panel (JAL-2864)
258    */
259  0 Object[] args = new Object[] {
260    new MouseEvent(af, 0, 0, 0, 0, 0, 1, true) };
261  0 PA.invokeMethod(af.alignPanel.overviewPanel,
262    "showPopupMenu(java.awt.event.MouseEvent)", args);
263   
264    /*
265    * set a selection group - potential memory leak if it retains
266    * a reference to the alignment
267    */
268  0 SequenceGroup sg = new SequenceGroup();
269  0 sg.setStartRes(0);
270  0 sg.setEndRes(100);
271  0 AlignmentI al = af.viewport.getAlignment();
272  0 for (int i = 0; i < al.getHeight(); i++)
273    {
274  0 sg.addSequence(al.getSequenceAt(i), false);
275    }
276  0 af.viewport.setSelectionGroup(sg);
277   
278    /*
279    * compute Tree and PCA (on all sequences, 100 columns)
280    */
281  0 af.openTreePcaDialog();
282  0 CalculationChooser dialog = af.alignPanel.getCalculationDialog();
283  0 dialog.openPcaPanel("BLOSUM62", dialog.getSimilarityParameters(true));
284  0 dialog.openTreePanel("BLOSUM62", dialog.getSimilarityParameters(false));
285   
286    /*
287    * wait until Tree and PCA have been computed
288    */
289  0 while (af.viewport.getCurrentTree() == null
290    || dialog.getPcaPanel().isWorking())
291    {
292  0 waitFor(10);
293    }
294   
295    /*
296    * give Swing time to add the PCA panel (?!?)
297    */
298  0 waitFor(100);
299    }
300   
301    /**
302    * Wait for waitMs miliseconds
303    *
304    * @param waitMs
305    */
 
306  0 toggle protected void waitFor(int waitMs)
307    {
308  0 try
309    {
310  0 Thread.sleep(waitMs);
311    } catch (InterruptedException e)
312    {
313    }
314    }
315   
316    /**
317    * Generates an alignment and saves it in a temporary file, to be loaded by
318    * Jalview. We use a peptide alignment (so Conservation and Quality are
319    * calculated), which is wide enough to ensure Consensus, Conservation and
320    * Occupancy have a significant memory footprint (if not removed from the
321    * heap).
322    *
323    * @return
324    * @throws IOException
325    */
 
326  0 toggle private File generateAlignment() throws IOException
327    {
328  0 File f = File.createTempFile("MemoryTest", "fa");
329  0 PrintStream ps = new PrintStream(f);
330  0 AlignmentGenerator ag = new AlignmentGenerator(false, ps);
331  0 int width = 100000;
332  0 int height = 100;
333  0 ag.generate(width, height, 0, 10, 15);
334  0 ps.close();
335  0 return f;
336    }
337    }