Skip to content

Commit e01ffa5

Browse files
committed
Merge #41 from branch 'metafacture-core-666-fixingMemoryLeak' of github.com:metafacture/metafacture-blog
2 parents 54f8464 + 81aa45c commit e01ffa5

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
---
2+
title: Fixing a memory leak
3+
description: "How we were affected by a memory leak and how we fixed it"
4+
date: "2025-03-17"
5+
authors: [{lastname: "Christoph",
6+
firstname: "Pascal"}]
7+
---
8+
9+
Some `metafacture-core` classes (e.g. the ubiquitous [`FileMap`](https://github.com/metafacture/metafacture-core/blob/master/metamorph/src/main/java/org/metafacture/metamorph/maps/FileMap.java) (which is invoked when loading a file as a table (like a CSV) in your ETL scripts)
10+
used uncloseable resources, which would leak memory, [resulting ultimately in a
11+
"OutOfMemoryError: Java heap space"](https://github.com/metafacture/metafacture-core/issues/666) since at least 2013 (back then the class was
12+
called `MapFile`).
13+
14+
## Preconditions
15+
16+
Affected were all usages of Metafacture which instantiate a `Flux` multiple times
17+
over the lifecycle of one JVM. While this is an obvious statement, we could
18+
experience the leaking of memory at the earliest since March 2021 when we
19+
changed how to start our ETL processes for lobid-resources: Back in the early days we invoked
20+
the ETL by starting a new JVM, run our workflow and terminate the JVM afterwards.
21+
Coming with the [Webhook in March 2021](https://github.com/hbz/lobid-resources/issues/1159)
22+
the JVM was not terminated after an ETL but listened further for incoming ETL
23+
triggering. In principle the JVM should have only been restarted when new code
24+
was to be deployed, but we soon discovered that restarting [our Play app](https://github.com/hbz/lobid-resources/tree/master/web)
25+
just before the weekly fresh and complete ETL of > 24 million documents improved
26+
the performance and averted some hanging processes or crashes.
27+
28+
We also have a monitoring tool installed on our servers which checks for terminated processes
29+
and restarts them automatically. This happened often after some
30+
more ETL processes were invoked (the "daily updates").
31+
It was unclear why these crashes appeared but by assigning more RAM and
32+
(automatically) restarting the whole JVM after crashes the ETL process went
33+
stable enough.
34+
35+
## Plotting the leak
36+
37+
Since 2025 we started the [Rheinland-Pfälzische Bibliografie (RPB)](https://github.com/hbz/rpb/).
38+
Here, ETL processes are triggered _every time_ a record is changed or created.
39+
This resulted very quickly in an
40+
[OutOfMemoryError](https://github.com/hbz/lobid-resources/issues/2121#issuecomment-2631355294).
41+
42+
Using Intellij IDEA we can easily produce a picture which shows the ever
43+
increasing demand of memory:
44+
![Screenshot of the graph showing the increasing demand of memory](./intellijIdeaGraphShowingMemoryLeak.png)
45+
At the beginning (at the left) we see that the CPU (green colored) is heavily used
46+
for all classes are compiled and loaded. Then we see how the Heap Memory (blue
47+
colored) is filled and after some time there are some spikes, indicating that
48+
the Garbage Collector was able to free some unused memory.
49+
We can see how the memory
50+
consumption is ascending nonetheless. Then a long line appears, lacking all
51+
spikes. This happens when there is less and less memory which the
52+
garbage collector could release. We can see 5 peaks where the CPU heavily
53+
works - this is the garbage collector trying to find some piece of memory
54+
to free - without much avail. The app crashes shortly after that (not to be
55+
seen).
56+
57+
## One fix, several problems solved
58+
59+
After [fixing the memory leak](https://github.com/metafacture/metafacture-core/commit/b32609307f75187a6a3822b8a951429c7fc924f3)
60+
the consumption of memory is normal, i.e. not ascending:
61+
![Screenshot of the graph showing normal consumption of memory](./intellijIdeaGraphShowingMemoryWithoutLeak.png)
62+
Every spike indicates that memory resources were freed, resulting in a stable
63+
rise and fall of CPU and memory usage. The memory leak is really fixed. 😀
64+
65+
Fixing the memory leak in Metafacture resolved some issues we've experienced:
66+
- lobid-resources: daily updates sometimes aborted - although this was not such a big thing because our monitor scripts could "heal" the update process automatically (by restarting the app). However, the updates now don't take e.g. 4h (counting from triggering the update until the successful ETL) but 0.45m, which is way faster.
67+
- Metafacture Playground: we had some [performance issues](https://github.com/metafacture/metafacture-playground/issues/194) which are now solved.
68+
- RPB: a situation arose where we could only ever add more memory to our VMs to counteract a crash of cataloguing - always fearing that not too much documents were ETLed before the daily restart of the cataloguing app.
69+
70+
## How to and credits
71+
72+
It is _one_ thing to discover a memory leak, but another thing to
73+
determine where the source of that leak _exactly_ is.
74+
I have to thank e.g. [Chris Braithwaite for his excellent blog post concerning Java memory leaks](https://medium.com/@chrisbrat_17048/java-memory-leak-investigation-8add1314e33b) to gain a bit more of the background of what a Java memory leak is.
75+
Very useful for me was the built-in profiler in Intellij IDEA. It not only
76+
has helped to plot the graphs (see above) to see at a glance that there indeed is a
77+
memory leak, but can also capture memory snapshots and profile the CPU usage
78+
to find the problematic classes. It would show something like this:
79+
![Screenshot of the graph showing normal consumption of memory](./intellijIdeaProfilingProcesses.png)
80+
If you have found the class where the memory leak most likely originates from
81+
you can make an educated guess of the statement which causes the problem,
82+
or/and by commenting in/out the likely code, tracking down the problematic code.
Loading
Loading
Loading

0 commit comments

Comments
 (0)