1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.sw4j.sample.memory.heap;
18
19 import java.io.PrintWriter;
20 import java.io.StringWriter;
21 import java.lang.management.ManagementFactory;
22 import java.util.Random;
23 import java.util.concurrent.ExecutionException;
24 import java.util.concurrent.ExecutorService;
25 import java.util.concurrent.Executors;
26 import java.util.concurrent.ScheduledExecutorService;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31 import javax.management.InstanceAlreadyExistsException;
32 import javax.management.MBeanRegistrationException;
33 import javax.management.MBeanServer;
34 import javax.management.MalformedObjectNameException;
35 import javax.management.NotCompliantMBeanException;
36 import javax.management.ObjectName;
37 import org.apache.commons.cli.CommandLine;
38 import org.apache.commons.cli.CommandLineParser;
39 import org.apache.commons.cli.DefaultParser;
40 import org.apache.commons.cli.HelpFormatter;
41 import org.apache.commons.cli.Option;
42 import org.apache.commons.cli.Options;
43 import org.apache.commons.cli.ParseException;
44
45
46
47
48
49 public class HeapSpace implements HeapSpaceMBean {
50
51 private static final Logger logger = Logger.getLogger(HeapSpace.class.getName());
52
53 private static final int DEFAULT_BLOCK_SIZE = 4096;
54
55 private static final int DEFAULT_CREATE_INTERVAL = 200;
56
57 private static final int DEFAULT_SHORT_LIFETIME = 1000;
58
59 private static final int DEFAULT_MEDIUM_LIFETIME = 150000;
60
61 private static final int DEFAULT_LONG_LIFETIME = 600000;
62
63 private static final double DEFAULT_MEDIUM_PROBABILITY = 15.0;
64
65 private static final double DEFAULT_LONG_PROBABILITY = 5.0;
66
67 private static final double MAX_PROBABILITY = 100.0;
68
69 private boolean stopped = false;
70
71 private int blockSize;
72
73 private int createInterval;
74
75 private int shortLifetime;
76
77 private int mediumLifetime;
78
79 private int longLifetime;
80
81 private double mediumProbability;
82
83 private double longProbability;
84
85 public static void main(String... args) throws Exception {
86 new HeapSpace().run(args);
87 }
88
89 public void run(String... args) throws InterruptedException, ExecutionException {
90 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
91 try {
92 ObjectName name = new ObjectName(HeapSpace.class.getPackage().getName(), "type",
93 HeapSpace.class.getSimpleName());
94 mbs.registerMBean(this, name);
95 } catch (InstanceAlreadyExistsException | MBeanRegistrationException |
96 MalformedObjectNameException | NotCompliantMBeanException exc) {
97 logger.log(Level.WARNING, "Problems creating MBean. Running without MBean creation.", exc);
98 }
99 PossibleConfiguration config = parseCommandLine(args);
100 if (config.hasValues()) {
101 run(config);
102 } else {
103 System.out.println(config.getMessage());
104 }
105 }
106
107
108
109
110
111
112
113 public void run(PossibleConfiguration config) throws InterruptedException, ExecutionException {
114 if (!config.hasValues()) {
115 throw new IllegalArgumentException("Need a configuration with values");
116 }
117 logger.info("Started HeapSpace");
118
119 blockSize = config.getBlockSize();
120 createInterval = config.getCreate();
121 shortLifetime = config.getShortLifetime();
122 mediumLifetime = config.getMediumLifetime();
123 longLifetime = config.getLongLifetime();
124 mediumProbability = config.getMediumProbability();
125 longProbability = config.getLongProbability();
126
127 Random rand = new Random();
128 final ScheduledExecutorService creationScheduler = Executors.newSingleThreadScheduledExecutor();
129 final ExecutorService hashExecutor = Executors.newFixedThreadPool(4);
130 final ScheduledExecutorService cleanUpScheduler = Executors.newSingleThreadScheduledExecutor();
131 Thread.setDefaultUncaughtExceptionHandler((Thread arg0, Throwable arg1) -> {
132 creationScheduler.shutdown();
133 hashExecutor.shutdown();
134 cleanUpScheduler.shutdown();
135 logger.warning("Exiting after unhandled exception in thread");
136 arg1.printStackTrace(System.err);
137 });
138 while (!stopped) {
139 ScheduledFuture<Allocator> futureAllocator =
140 creationScheduler.schedule(() -> new Allocator(1 + rand.nextInt(blockSize)),
141 rand.nextInt(getCreateInterval()), TimeUnit.MILLISECONDS);
142 final Allocator allocator = futureAllocator.get();
143 hashExecutor.submit(() -> {
144 allocator.calculateHash();
145 logger.info(String.format("Created allocator with hash %s", allocator.getHash()));
146 int cleanUpTime = rand.nextInt(shortLifetime);
147 String cleanUpMessage = "Released short lived hash %s";
148 double prob = MAX_PROBABILITY * rand.nextDouble();
149 if (prob >= MAX_PROBABILITY - longProbability) {
150 cleanUpTime = mediumLifetime + rand.nextInt(longLifetime - mediumLifetime);
151 cleanUpMessage = "Released long lived hash %s";
152 } else if (prob >= MAX_PROBABILITY - (longProbability + mediumProbability)) {
153 cleanUpTime = shortLifetime + rand.nextInt(mediumLifetime - shortLifetime);
154 cleanUpMessage = "Released medium lived hash %s";
155 }
156 final String format = cleanUpMessage;
157 cleanUpScheduler.schedule(() -> {
158 logger.info(String.format(format, allocator.getHash()));
159 }, cleanUpTime, TimeUnit.MILLISECONDS);
160 return null;
161 });
162 }
163 }
164
165
166
167
168
169
170
171 public PossibleConfiguration parseCommandLine(String... args) {
172 Options options = new Options();
173
174 options.addOption(Option
175 .builder("h")
176 .longOpt("help")
177 .optionalArg(true)
178 .desc("Print this help")
179 .build());
180
181 options.addOption(Option
182 .builder("b")
183 .longOpt("block-size")
184 .optionalArg(true)
185 .hasArg(true)
186 .argName("size")
187 .desc(String.format("The max block size of memory to reserve (default: %d)", DEFAULT_BLOCK_SIZE))
188 .build());
189
190 options.addOption(Option
191 .builder("c")
192 .longOpt("create")
193 .optionalArg(true)
194 .hasArg(true)
195 .argName("interval")
196 .desc(String.format("The max interval (in ms) between object creation (default: %d)",
197 DEFAULT_CREATE_INTERVAL))
198 .build());
199
200 options.addOption(Option
201 .builder("s")
202 .longOpt("short-time")
203 .optionalArg(true)
204 .hasArg(true)
205 .argName("duration")
206 .desc(String.format("The max lifetime (in ms) of a short lived object (default: %d)",
207 DEFAULT_SHORT_LIFETIME))
208 .build());
209
210 options.addOption(Option
211 .builder("m")
212 .longOpt("medium-time")
213 .optionalArg(true)
214 .hasArg(true)
215 .argName("duration")
216 .desc(String.format("The max lifetime (in ms) of a medium lived object (default: %d)",
217 DEFAULT_MEDIUM_LIFETIME))
218 .build());
219
220 options.addOption(Option
221 .builder("l")
222 .longOpt("long-time")
223 .optionalArg(true)
224 .hasArg(true)
225 .argName("duration")
226 .desc(String.format("The max lifetime (in ms) of a long lived object (default: %d)",
227 DEFAULT_LONG_LIFETIME))
228 .build());
229
230 options.addOption(Option
231 .builder()
232 .longOpt("med-prob")
233 .optionalArg(true)
234 .hasArg(true)
235 .argName("probability")
236 .desc(String.format("The probability (in %%) to create a medium lived object (default: %3.1f)",
237 DEFAULT_MEDIUM_PROBABILITY))
238 .build());
239
240 options.addOption(Option
241 .builder()
242 .longOpt("long-prob")
243 .optionalArg(true)
244 .hasArg(true)
245 .argName("probability")
246 .desc(String.format("The probability (in %%) to create a long lived object (default: %3.1f)",
247 DEFAULT_LONG_PROBABILITY))
248 .build());
249
250 CommandLineParser clParser = new DefaultParser();
251 CommandLine cl = new CommandLine.Builder().build();
252 try {
253 cl = clParser.parse(options, args, true);
254 } catch (ParseException pex) {
255 logger.log(Level.WARNING, "Problems while parsing the command line", pex);
256 }
257
258 int block = DEFAULT_BLOCK_SIZE;
259 if (cl.hasOption("b")) {
260 try {
261 block = Integer.parseInt(cl.getOptionValue("b"));
262 } catch (NumberFormatException nfex) {
263 logger.fine("Cannot parse block size. Using default.");
264 }
265 }
266
267 int create = DEFAULT_CREATE_INTERVAL;
268 if (cl.hasOption("c")) {
269 try {
270 create = Integer.parseInt(cl.getOptionValue("c"));
271 } catch (NumberFormatException nfex) {
272 logger.fine("Cannot parse creation interval. Using default.");
273 }
274 }
275
276 int shortLife = DEFAULT_SHORT_LIFETIME;
277 if (cl.hasOption("s")) {
278 try {
279 shortLife = Integer.parseInt(cl.getOptionValue("s"));
280 } catch (NumberFormatException nfex) {
281 logger.fine("Cannot parse short lifetime. Using default.");
282 }
283 }
284
285 int medLife = DEFAULT_MEDIUM_LIFETIME;
286 if (cl.hasOption("m")) {
287 try {
288 medLife = Integer.parseInt(cl.getOptionValue("m"));
289 } catch (NumberFormatException nfex) {
290 logger.fine("Cannot parse medium lifetime. Using default.");
291 }
292 }
293
294 int longLife = DEFAULT_LONG_LIFETIME;
295 if (cl.hasOption("l")) {
296 try {
297 longLife = Integer.parseInt(cl.getOptionValue("l"));
298 } catch (NumberFormatException nfex) {
299 logger.fine("Cannot parse long lifetime. Using default.");
300 }
301 }
302
303 double medProb = DEFAULT_MEDIUM_PROBABILITY;
304 if (cl.hasOption("med-prob")) {
305 try {
306 medProb = Double.parseDouble(cl.getOptionValue("med-prob"));
307 } catch (NumberFormatException nfex) {
308 logger.fine("Cannot parse medium lived probability. Using default");
309 }
310 }
311
312 double longProb = DEFAULT_LONG_PROBABILITY;
313 if (cl.hasOption("long-prob")) {
314 try {
315 longProb = Double.parseDouble(cl.getOptionValue("long-prob"));
316 } catch (NumberFormatException nfex) {
317 logger.fine("Cannot parse long lived probability. Using default");
318 }
319 }
320
321 PossibleConfiguration config;
322 if (cl.hasOption("help")) {
323 StringWriter sw = new StringWriter();
324 PrintWriter pw = new PrintWriter(sw);
325 HelpFormatter helper = new HelpFormatter();
326 helper.printHelp(pw, 80, "HeapSpace",
327 "A small application that will (most likely) create an OutOfMemoryError " +
328 "with message \"Java heap space\"",
329 options, 1, 2, "", true);
330 config = new PossibleConfiguration(sw.toString());
331 } else {
332 StringBuilder sb = new StringBuilder();
333 if (block < 1) {
334 appendNewLineIfNeeded(sb).append("The block size (-b) must be greater than 0.");
335 }
336 if (create < 1) {
337 appendNewLineIfNeeded(sb).append("The creation interval (-c) must be greater than 0.");
338 }
339 if (shortLife < 1) {
340 appendNewLineIfNeeded(sb).append("The short life time (-s) must be greater than 0.");
341 }
342 if (medLife < 1) {
343 appendNewLineIfNeeded(sb).append("The medium life time (-m) must be greater than 0.");
344 }
345 if (longLife < 1) {
346 appendNewLineIfNeeded(sb).append("The long life time (-l) must be greater than 0.");
347 }
348 if (medProb < 0.0) {
349 appendNewLineIfNeeded(sb).append("The medium probability (--med-prob) must be greater than 0.0.");
350 }
351 if (longProb < 0.0) {
352 appendNewLineIfNeeded(sb).append("The long probability (--long-prob) must be greater than 0.0.");
353 }
354 if (shortLife > medLife) {
355 appendNewLineIfNeeded(sb).append("The short life time must be lower than the medium life time.");
356 }
357 if (medLife > longLife) {
358 appendNewLineIfNeeded(sb).append("The medium life time must be lower than the long life time.");
359 }
360 if (medProb + longProb > MAX_PROBABILITY) {
361 appendNewLineIfNeeded(sb).append(String.format("The sum of medium probability and long probability " +
362 "may not be larger than %4.1f.", MAX_PROBABILITY));
363 }
364 if (sb.length() > 0) {
365 config = new PossibleConfiguration(sb.toString());
366 } else {
367 config = new PossibleConfiguration(block, create, shortLife, medLife, longLife, medProb, longProb);
368 }
369 }
370
371 return config;
372 }
373
374 private StringBuilder appendNewLineIfNeeded(StringBuilder sb) {
375 if (sb.length() > 0) {
376 sb.append("\n");
377 }
378 return sb;
379 }
380
381
382
383
384 @Override
385 public void stop() {
386 stopped = true;
387 }
388
389 @Override
390 public int getBlockSize() {
391 return blockSize;
392 }
393
394 @Override
395 public void setBlockSize(int blockSize) {
396 this.blockSize = blockSize;
397 }
398
399 @Override
400 public void setCreateInterval(int millis) {
401 createInterval = millis;
402 }
403
404 @Override
405 public int getCreateInterval() {
406 return createInterval;
407 }
408
409 @Override
410 public int getShortLifetime() {
411 return shortLifetime;
412 }
413
414 @Override
415 public void setShortLifetime(int shortLifetime) {
416 this.shortLifetime = shortLifetime;
417 }
418
419 @Override
420 public int getMediumLifetime() {
421 return mediumLifetime;
422 }
423
424 @Override
425 public void setMediumLifetime(int mediumLifetime) {
426 this.mediumLifetime = mediumLifetime;
427 }
428
429 @Override
430 public int getLongLifetime() {
431 return longLifetime;
432 }
433
434 @Override
435 public void setLongLifetime(int longLifetime) {
436 this.longLifetime = longLifetime;
437 }
438
439 @Override
440 public void setMediumProbability(double prob) {
441 mediumProbability = prob;
442 }
443
444 @Override
445 public double getMediumProbability() {
446 return mediumProbability;
447 }
448
449 @Override
450 public void setLongProbability(double prob) {
451 longProbability = prob;
452 }
453
454 @Override
455 public double getLongProbability() {
456 return longProbability;
457 }
458
459 }