Reproducing Heap Exhaustion
Bootstrap a Spring Boot application and add the web starter dependancy:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Implement an endpoint that continuously reserves large blocks of memory:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/stress")
public class HeapExhaustionEndpoint {
private final List<byte[]> payloadBuffer = new ArrayList<>();
@GetMapping("/consume")
public String exhaustHeap() {
int chunk = 2 * 1024 * 1024; // 2 MB per allocation
while (true) {
payloadBuffer.add(new byte[chunk]);
}
}
}
Package the application:
mvn clean package -DskipTests
Move the resulting JAR to the target server.
Running with Constrained Heap
Limit the heap to 48 MB so the error surfaces quickly:
nohup java -Xms48m -Xmx48m -jar target/app.jar > application.log 2>&1 &
Flag details:
-Xms48m -Xmx48mlocks initial and maximum heap size to 48 MB.nohup ... &runs the process in the background and shields it from hangup signals.> application.log 2>&1redirects stderr into stdout and writes both to the same log file.
Inducing the Failure
Call the endpoint from the host or any reachable client:
curl http://localhost:8080/stress/consume
Once the heap saturates, the framework returns an HTTP 500 response:
{"timestamp":"2024-07-15T06:47:38.083+00:00","status":500,"error":"Internal Server Error","path":"/stress/consume"}
Log Examination
Inspect the log for the underlying cause:
tail -n 100 application.log
You will find java.lang.OutOfMemoryError: Java heap space together with the associated stack trace.
Acquiring Heap and Thread Dumps
Identify the JVM process ID:
jps -lv
Or alternatively:
ps aux | grep java
Generate a binary heap dump:
jmap -dump:live,format=b,file=heap.hprof <pid>
Record the thread state:
jstack -l <pid> > threads.txt
Heap Dump Inspection with MAT
Load heap.hprof into Eclipse Memory Analyzer. When the import dialog appears, choose Leak Suspects Report. MAT automatically identifies the object graph path that retains the most memory.
In this case, the report highlights an ArrayList instance held by HeapExhaustionEndpoint that keeps a growing set of byte[] objects. Clicking Details on the dominant suspect reveals the retained heap breakdown, showing the 2 MB arrays consuming nearly all available space.
Other useful MAT views:
- Histogram: Tallies instances per class. Expect
byte[]to rank highest. - Dominator Tree: Surfaces the largest retained objects and their dependents. The controller’s list appears at the top, referencing every allocated array.
- Top Consumers: Aggregates memory by class and package to highlight heavy components.
- Component Report: Analyzes retention for a specific package or class-loader boundary.
Opening the Dominator Tree confirms that HeapExhaustionEndpoint.payloadBuffer is the primary GC root path preventing collection, with each node representing a 2 MB byte allocation.