blob: 54a5eb1cc6fb40233afafdf3de06ceaa32335db6 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NestedAroundClosureMemoryLeakTest {
public static void main(String[] args) throws Exception {
if (args.length > 0 && "oom".equals(args[0]))
testNoMemoryLeak_SystemShouldNotRunOutOfMemory();
else
testNoMemoryLeak_InheritableThreadLocalCleared();
}
/**
* Tests that the inheritable thread-locals of the spawned threads are either null or contain all null elements
*/
public static void testNoMemoryLeak_InheritableThreadLocalCleared() throws Exception {
int numThreadPools = 1;
List<ExecutorService> executorServices = createExecutorServicesWithFixedThreadPools(numThreadPools);
try {
executeTasksAndGC(executorServices);
Field mapField = Thread.class.getDeclaredField("inheritableThreadLocals");
mapField.setAccessible(true);
Set<Thread> threads = Thread.getAllStackTraces().keySet();
threads.stream()
.filter(thread -> thread.getName().contains("pool"))
.forEach(thread -> {
try {
Object inheritableThreadLocals = mapField.get(thread);
if (inheritableThreadLocals != null) {
Field tableField = inheritableThreadLocals.getClass().getDeclaredField("table");
tableField.setAccessible(true);
Object[] inheritableThreadLocalTable = (Object[]) tableField.get(inheritableThreadLocals);
if (inheritableThreadLocalTable != null) {
for (Object entry : inheritableThreadLocalTable)
assert entry == null : "All inheritable thread-locals should be null after GC";
}
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
});
System.out.println("Test passed - all inheritable thread-locals are null after GC");
}
finally {
for (ExecutorService executorService : executorServices)
executorService.shutdown();
}
}
/**
* Executes tasks in multiple threads, using one executor service with a fixed thread pool of size 1 per task. This
* ensures that each spawned thread gets initialised for the first time and allocates memory for inheritable
* thread-locals, exposing possible memory leaks when running @AspectJ aspects with non-inlined, nested around advices
* in multi-thread situations.
* <p>
* If each thread allocates memory for a stack of around closures (memory leak case) according to
* <a href="https://github.com/eclipse-aspectj/aspectj/issues/288">GitHub issue 288</a>, the program will run out of
* memory. When fixed, this should no longer happen.
* <p>
* Run this test e.g. with {@code -Xmx1G} for 10 x 128 MB memory consumption to ensure an out of memory error in the
* leak case. Any other appropriate combination of number of threads and memory limit is also OK.
*/
public static void testNoMemoryLeak_SystemShouldNotRunOutOfMemory() throws Exception {
int numThreadPools = 5;
List<ExecutorService> executorServices = createExecutorServicesWithFixedThreadPools(numThreadPools);
try {
executeTasksAndGC(executorServices);
System.out.println("Test passed - no OutOfMemoryError due to inheritable thread-locals memory leak");
}
finally {
for (ExecutorService executorService : executorServices)
executorService.shutdown();
}
}
private static List<ExecutorService> createExecutorServicesWithFixedThreadPools(int count) {
List<ExecutorService> executorServiceList = new ArrayList<>(count);
for (int i = 0; i < count; i++)
executorServiceList.add(Executors.newFixedThreadPool(1));
return executorServiceList;
}
private static void executeTasksAndGC(List<ExecutorService> executorServices) throws Exception {
for (ExecutorService executorService : executorServices)
new MemoryHog(executorService).doSomething();
System.out.println("Finished executing tasks");
// Best effort GC
System.gc();
System.out.println("Finished executing GC");
// Sleep to take a memory dump
// Thread.sleep(500000);
}
}
|