Reputation:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class ExecutorServiceTest {
public static void main(String args[]) {
new ExecutorServiceTest();
}
public ExecutorServiceTest() {
while (true) {
action();
}
}
public String action() {
String string = "";
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new Callable() {
@Override
public String call() {
return randomString();
}
});
try {
string = future.get(1, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
}
future.cancel(true);
executor.shutdownNow();
return string;
}
public String randomString() {
return "asdhkashdkjshakdasdsahdka";
}
}
If you open up a task manager window and then run this class, you will notice that Java instantly uses a ton of memory. I have searched and debugged the class thoroughly but cannot find the memory leak.
What is causing future.get(1, TimeUnit.MILLISECONDS);
to use up all the system memory and then crash?
Upvotes: 2
Views: 3586
Reputation: 100289
The ThreadPoolExecutor
class has non-empty finalize()
method (which performs the pool shutdown), so when garbage-collected it first goes to the finalizer queue (which is basically linked list) and processed by separate "Finalizer" thread. Even if the pool has no active tasks, shutting it down is quite slow. Depending on the hardware, OS and running conditions it's possible that new finalizers are added faster than processed by Finalizer thread, thus you may expect constant memory growth. In general you should not create and shutdown executors too fast.
Upvotes: 7
Reputation: 7782
I think the culprit is here:
while (true) {
action();
}
This is spawning new threads aggressively (this is an expensive operation) and, IMHO, this leads to the memory issues and the crash.
Also, I think this is a bad example of using a single threaded executor. It is more suitable for serial execution of tasks in a single background thread. If you need to add a big number of new tasks, that have to be executed simultaneously, it's better to use a thread pool. Controlling the number of worker threads is going to prevent the program from running out of memory.
Working code:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class ExecutorServiceTest {
ExecutorService executor = Executors.newSingleThreadExecutor();
public static void main(String args[]) {
new ExecutorServiceTest();
}
public ExecutorServiceTest() {
while (true) {
action();
}
}
public String action() {
String string = "";
if (executor.isShutdown()) {
Future<String> future = executor.submit(new Callable() {
@Override
public String call() {
return randomString();
}
});
try {
string = future.get(1, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
System.out.println(ex);
}
future.cancel(true);
executor.shutdownNow();
}
return string;
}
public String randomString() {
return "asdhkashdkjshakdasdsahdka";
}
}
Upvotes: 0