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