boolean (@NotNull StringBuilder log, @NotNull Runnable runWhileWaiting, @NotNull BooleanSupplier until) { long freeMemory = Runtime.getRuntime().freeMemory(); log.append("Free memory: " + freeMemory + "\n"); int liveChunks = 0; ReferenceQueue<Object> queue = new ReferenceQueue<>(); List<SoftReference<?>> list = new ArrayList<>(); try { for (int i = 0; i < 1000 && !until.getAsBoolean(); i++) { runWhileWaiting.run(); while (queue.poll() != null) { liveChunks--; } // full gc is caused by allocation of large enough array below, SoftReference will be cleared after two full gc int bytes = Math.min((int)(Runtime.getRuntime().totalMemory() / 20), Integer.MAX_VALUE / 2); log.append("Iteration " + i + ", allocating new byte[" + bytes + "]" + ", live chunks: " + liveChunks + ", free memory: " + Runtime.getRuntime().freeMemory() + "\n"); list.add(new SoftReference<Object>(new byte[bytes], queue)); liveChunks++; if (i > 0 && i % 100 == 0 && !until.getAsBoolean()) { log.append(" Calling System.gc()\n"); System.gc(); } } } catch (OutOfMemoryError e) { int size = list.size(); list.clear(); //noinspection CallToPrintStackTrace e.printStackTrace(); System.err.println("Log: " + log + "freeMemory() now: " + Runtime.getRuntime().freeMemory() + "; list.size(): " + size); System.err.println(ThreadDumper.dumpThreadsToString()); throw e; } finally { // do not leave a chance for our created SoftReference's content to lie around until next full GC for (Reference<?> createdReference : list) { createdReference.clear(); } } return until.getAsBoolean(); }