ADDITIONAL SYSTEM INFORMATION :
java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)
A DESCRIPTION OF THE PROBLEM :
When I add a lot of tasks to a java.util.Timer, the timer creates a memory leak. The behaviour is partly documented and solved by https://bugs.openjdk.java.net/browse/JDK-4481072.
However, looking at the source code of TaskQueue (java/util/Timer.java, starting at line 569), I can see that the backing array is extended in the add() method (line 599), but there is no code to shrink the array when TimerTasks are cancelled. Most notably, the purge() method, introduced as a reaction to the bug report linked above, does not shrink the array.
This means that the array keeps growing and growing, until the JVM runs out of memory.
I observed similiar behaviour with ScheduledExecutorService.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a java.util.Timer and, in a loop, add tasks to be executed in 10 minutes and cancel them immediately. The cancel() is not even required, but adds to the drama.
After the loop, call purge().
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The memory requirement should be what is required for one TimerTask. As each task is cancelled immediately, there is never more than one active task. https://bugs.openjdk.java.net/browse/JDK-4481072 already documents that cancelled tasks are not removed from the queue, unless purge() is called.
After the purge(), the memory requirement should be back to what it was before adding tasks, because the TaskQueue is empty now.
For ScheduledExecutorService, there is no purge(), so the memory requirement should shrink on its own.
ACTUAL -
Even after purge(), the memory requirement stays (almost) what it was before the purge().
For ScheduledExecutorService, even a call to System.gc() does not make as much of a difference as it should.
---------- BEGIN SOURCE ----------
import org.junit.Test;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TimerTest {
@Test
public void testTimer() {
Timer timer = new Timer();
long startMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long maxMemory = startMemory;
for(int i = 0; i < 1000000; i++) {
TimerTask task = new TimerTask() {
@Override
public void run() {
// NoOp
}
};
timer.schedule(task, TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES));
task.cancel();
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
if(currentMemory > maxMemory) {
maxMemory = currentMemory;
}
}
timer.purge();
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("initial memory: " + startMemory);
System.out.println("maximum memory: " + maxMemory);
System.out.println("current memory: " + currentMemory);
}
@Test
public void testService() {
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
long startMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long maxMemory = startMemory;
for(int i = 0; i < 1000000; i++) {
ScheduledFuture<?> task = timer.schedule(() -> {
// NoOp
}, 10, TimeUnit.MINUTES);
task.cancel(false);
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
if(currentMemory > maxMemory) {
maxMemory = currentMemory;
}
}
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("initial memory: " + startMemory);
System.out.println("maximum memory: " + maxMemory);
System.out.println("current memory: " + currentMemory);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
java.util.Timer was supposed to be deprecated in Java 9 (see https://stackoverflow.com/questions/2213109/java-util-timer-is-it-deprecated). So, maybe it is time to do so.
I am not aware of an alternative to the scheduler service.
FREQUENCY : always
java version "1.8.0_51"
Java(TM) SE Runtime Environment (build 1.8.0_51-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.51-b03, mixed mode)
A DESCRIPTION OF THE PROBLEM :
When I add a lot of tasks to a java.util.Timer, the timer creates a memory leak. The behaviour is partly documented and solved by https://bugs.openjdk.java.net/browse/JDK-4481072.
However, looking at the source code of TaskQueue (java/util/Timer.java, starting at line 569), I can see that the backing array is extended in the add() method (line 599), but there is no code to shrink the array when TimerTasks are cancelled. Most notably, the purge() method, introduced as a reaction to the bug report linked above, does not shrink the array.
This means that the array keeps growing and growing, until the JVM runs out of memory.
I observed similiar behaviour with ScheduledExecutorService.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a java.util.Timer and, in a loop, add tasks to be executed in 10 minutes and cancel them immediately. The cancel() is not even required, but adds to the drama.
After the loop, call purge().
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The memory requirement should be what is required for one TimerTask. As each task is cancelled immediately, there is never more than one active task. https://bugs.openjdk.java.net/browse/JDK-4481072 already documents that cancelled tasks are not removed from the queue, unless purge() is called.
After the purge(), the memory requirement should be back to what it was before adding tasks, because the TaskQueue is empty now.
For ScheduledExecutorService, there is no purge(), so the memory requirement should shrink on its own.
ACTUAL -
Even after purge(), the memory requirement stays (almost) what it was before the purge().
For ScheduledExecutorService, even a call to System.gc() does not make as much of a difference as it should.
---------- BEGIN SOURCE ----------
import org.junit.Test;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TimerTest {
@Test
public void testTimer() {
Timer timer = new Timer();
long startMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long maxMemory = startMemory;
for(int i = 0; i < 1000000; i++) {
TimerTask task = new TimerTask() {
@Override
public void run() {
// NoOp
}
};
timer.schedule(task, TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES));
task.cancel();
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
if(currentMemory > maxMemory) {
maxMemory = currentMemory;
}
}
timer.purge();
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("initial memory: " + startMemory);
System.out.println("maximum memory: " + maxMemory);
System.out.println("current memory: " + currentMemory);
}
@Test
public void testService() {
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
long startMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long maxMemory = startMemory;
for(int i = 0; i < 1000000; i++) {
ScheduledFuture<?> task = timer.schedule(() -> {
// NoOp
}, 10, TimeUnit.MINUTES);
task.cancel(false);
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
if(currentMemory > maxMemory) {
maxMemory = currentMemory;
}
}
long currentMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("initial memory: " + startMemory);
System.out.println("maximum memory: " + maxMemory);
System.out.println("current memory: " + currentMemory);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
java.util.Timer was supposed to be deprecated in Java 9 (see https://stackoverflow.com/questions/2213109/java-util-timer-is-it-deprecated). So, maybe it is time to do so.
I am not aware of an alternative to the scheduler service.
FREQUENCY : always