5d7f14c83deeeb30747461324f56a9b7614f62b4
[jalview.git] / MemorySetting.java
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.bin;
22
23 /**
24  * Methods to decide on appropriate memory setting for Jalview based on two
25  * optionally provided values: jvmmempc - the maximum percentage of total
26  * physical memory to allocate, and jvmmemmax - the maximum absolute amount of
27  * physical memory to allocate. These can be provided as arguments or system
28  * properties. Other considerations such as minimum application requirements and
29  * leaving space for OS are used too.
30  * 
31  * @author bsoares
32  *
33  */
34 public class MemorySetting
35 {
36   public static final String MAX_HEAPSIZE_PERCENT_PROPERTY_NAME = "jvmmempc";
37
38   public static final String MAX_HEAPSIZE_PROPERTY_NAME = "jvmmemmax";
39
40   private static final int MAX_HEAPSIZE_PERCENT_DEFAULT = 90; // 90%
41
42   private static final long GIGABYTE = 1073741824; // 1GB
43
44   public static final long LEAVE_FREE_MIN_MEMORY = GIGABYTE / 2;
45
46   public static final long APPLICATION_MIN_MEMORY = GIGABYTE / 2;
47
48   private static final long MAX_HEAPSIZE_GB_DEFAULT = 32;
49
50   private static final long NOMEM_MAX_HEAPSIZE_GB_DEFAULT = 8;
51
52   protected static boolean logToClassChecked = false;
53
54   public static long getMemorySetting()
55   {
56     return getMemorySetting(null, null);
57   }
58
59   /**
60    * Decide on appropriate memory setting for Jalview based on the two arguments
61    * values: jvmmempc - the maximum percentage of total physical memory to
62    * allocate, and jvmmemmax - the maximum absolute amount of physical memory to
63    * allocate. These can be provided as arguments. If not provided as arguments
64    * (or set as null) system properties will be used instead (if set). The
65    * memory setting returned will be the lower of the two values. If either of
66    * the values are not provided then defaults will be used (jvmmempc=90,
67    * jvmmemmax=32GB). If total physical memory can't be ascertained when
68    * jvmmempc was set or neither jvmmempc nor jvmmemmax were set, then jvmmemmax
69    * defaults to a much safer 8GB. In this case explicitly setting jvmmemmax and
70    * not setting jvmmempc can set a higher memory for Jalview. The calculation
71    * also tries to ensure 0.5GB memory for the OS, but also tries to ensure at
72    * least 0.5GB memory for Jalview (which takes priority over the OS) If there
73    * is less then 0.5GB of physical memory then the total physical memory is
74    * used for Jalview.
75    * 
76    * @param jvmmemmaxarg
77    *          Maximum value of memory to set. This can be a numeric string
78    *          optionally followed by "b", "k", "m", "g", "t" (case insensitive)
79    *          to indicate bytes, kilobytes, megabytes, gigabytes, terabytes
80    *          respectively. If null a default value of 32G will be used. If null
81    *          and either physical memory can't be determined then the default is
82    *          8GB.
83    * @param jvmmempcarg
84    *          Max percentage of physical memory to use. Defaults to "90".
85    * 
86    * @return The amount of memory (in bytes) to allocate to Jalview
87    */
88   public static long getMemorySetting(String jvmmemmaxarg,
89           String jvmmempcarg)
90   {
91     // actual Xmx value-to-be
92     long maxMemLong = -1;
93
94     // (absolute) jvmmaxmem setting, start with default
95     long memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE;
96     if (jvmmemmaxarg == null)
97     {
98       jvmmemmaxarg = System.getProperty(MAX_HEAPSIZE_PROPERTY_NAME);
99     }
100     String jvmmemmax = jvmmemmaxarg;
101     if (jvmmemmax != null && jvmmemmax.length() > 0)
102     {
103       long multiplier = 1;
104       switch (jvmmemmax.toLowerCase().substring(jvmmemmax.length() - 1))
105       {
106       case "t":
107         multiplier = 1099511627776L; // 2^40
108         jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1);
109         break;
110       case "g":
111         multiplier = 1073741824; // 2^30
112         jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1);
113         break;
114       case "m":
115         multiplier = 1048576; // 2^20
116         jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1);
117         break;
118       case "k":
119         multiplier = 1024; // 2^10
120         jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1);
121         break;
122       case "b":
123         multiplier = 1; // 2^0
124         jvmmemmax = jvmmemmax.substring(0, jvmmemmax.length() - 1);
125         break;
126       default:
127         break;
128       }
129
130       // parse the arg
131       try
132       {
133         memmax = Long.parseLong(jvmmemmax);
134       } catch (NumberFormatException e)
135       {
136         memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE;
137         System.out.println("MemorySetting Property '"
138                 + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg
139                 + "') badly formatted, using default ("
140                 + MAX_HEAPSIZE_GB_DEFAULT + "g).");
141       }
142
143       // apply multiplier if not too big (i.e. bigger than a long)
144       if (Long.MAX_VALUE / memmax < multiplier)
145       {
146         memmax = MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE;
147         System.out.println("MemorySetting Property '"
148                 + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg
149                 + ") too big, using default (" + MAX_HEAPSIZE_GB_DEFAULT
150                 + "g).");
151       }
152       else
153       {
154         memmax = multiplier * memmax;
155       }
156
157       // check at least minimum value (this accounts for negatives too)
158       if (memmax < APPLICATION_MIN_MEMORY)
159       {
160         memmax = APPLICATION_MIN_MEMORY;
161         System.out.println("MemorySetting Property '"
162                 + MAX_HEAPSIZE_PROPERTY_NAME + "' (" + jvmmemmaxarg
163                 + ") too small, using minimum (" + APPLICATION_MIN_MEMORY
164                 + ").");
165       }
166
167     }
168     else
169     {
170       // no need to warn if no setting
171       // System.out.println("MemorySetting Property '" + maxHeapSizeProperty
172       // + "' not
173       // set.");
174     }
175
176     // get max percent of physical memory, starting with default
177     float percent = MAX_HEAPSIZE_PERCENT_DEFAULT;
178     if (jvmmempcarg == null)
179     {
180       jvmmempcarg = System.getProperty(MAX_HEAPSIZE_PERCENT_PROPERTY_NAME);
181     }
182     String jvmmempc = jvmmempcarg;
183     long mempc = -1;
184     try
185     {
186       if (jvmmempc != null)
187       {
188         float trypercent = Float.parseFloat(jvmmempc);
189         if (0 < trypercent && trypercent <= 100f)
190         {
191           percent = trypercent;
192         }
193         else
194         {
195           System.out.println("MemorySetting Property '"
196                   + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME
197                   + "' should be in range 1..100. Using default " + percent
198                   + "%");
199         }
200       }
201     } catch (NumberFormatException e)
202     {
203       System.out.println("MemorySetting property '"
204               + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg
205               + ") badly formatted");
206     }
207
208     // catch everything in case of no com.sun.management.OperatingSystemMXBean
209     boolean memoryPercentError = false;
210     try
211     {
212       long physicalMem = GetMemory.getPhysicalMemory();
213       if (physicalMem > APPLICATION_MIN_MEMORY)
214       {
215         // try and set at least applicationMinMemory and thereafter ensure
216         // leaveFreeMinMemory is left for the OS
217
218         mempc = (long) ((physicalMem / 100F) * percent);
219
220         // check for memory left for OS
221         boolean reducedmempc = false;
222         if (physicalMem - mempc < LEAVE_FREE_MIN_MEMORY)
223         {
224           mempc = physicalMem - LEAVE_FREE_MIN_MEMORY;
225           reducedmempc = true;
226           System.out.println("MemorySetting Property '"
227                   + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' (" + jvmmempcarg
228                   + ") too large. Leaving free space for OS and reducing to ("
229                   + mempc + ").");
230         }
231
232         // check for minimum application memsize
233         if (mempc < APPLICATION_MIN_MEMORY)
234         {
235           if (reducedmempc)
236           {
237             System.out.println("Reduced MemorySetting (" + mempc
238                     + ") too small. Increasing to application minimum ("
239                     + APPLICATION_MIN_MEMORY + ").");
240           }
241           else
242           {
243             System.out.println("MemorySetting Property '"
244                     + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' ("
245                     + jvmmempcarg + ") too small. Using minimum ("
246                     + APPLICATION_MIN_MEMORY + ").");
247           }
248           mempc = APPLICATION_MIN_MEMORY;
249         }
250       }
251       else
252       {
253         // not enough memory for application, just try and grab what we can!
254         mempc = physicalMem;
255         System.out.println(
256                 "Not enough physical memory for application. Ignoring MemorySetting Property '"
257                         + MAX_HEAPSIZE_PERCENT_PROPERTY_NAME + "' ("
258                         + jvmmempcarg
259                         + "). Using maximum memory available ("
260                         + physicalMem + ").");
261       }
262
263     } catch (Throwable t)
264     {
265       memoryPercentError = true;
266       System.out.println(
267               "Problem calling GetMemory.getPhysicalMemory(). Likely to be problem with com.sun.management.OperatingSystemMXBean");
268       t.printStackTrace();
269     }
270
271     // In the case of an error reading the percentage of physical memory (when
272     // jvmmempc was set OR neither jvmmempc nor jvmmemmax were set), let's cap
273     // maxMemLong to 8GB
274     if (memoryPercentError && mempc == -1
275             && !(jvmmempcarg == null && jvmmemmaxarg != null) // the same as
276                                                               // (jvmmempcarg !=
277                                                               // null ||
278                                                               // (jvmmempcarg ==
279                                                               // null &&
280                                                               // jvmmemmaxarg
281                                                               // == null))
282             && memmax > NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE)
283     {
284       System.out.println(
285               "Capping maximum memory to " + NOMEM_MAX_HEAPSIZE_GB_DEFAULT
286                       + "g due to failure to read physical memory size.");
287       memmax = NOMEM_MAX_HEAPSIZE_GB_DEFAULT * GIGABYTE;
288     }
289
290     if (mempc == -1) // percentage memory not set
291     {
292       maxMemLong = memmax;
293     }
294     else
295     {
296       maxMemLong = Math.min(mempc, memmax);
297     }
298
299     return maxMemLong;
300   }
301
302 }